Diagnostic events and caller correlation

Status: draft.

This is the local event contract for judgevet 0.8.0. It does not describe a vendor API or promote live-service evidence. Built-in event names form a closed set: http.call and mcp.runtime. Application-defined events are separate.

Rendering and ownership

Library imports and unconfigured calls stay silent. Applications own logging configuration. CLI and MCP configure stderr; their default info level omits routine HTTP events. Set JEV_LOG__LEVEL=debug to include those events. JEV_LOG__FORMAT=json selects one JSON object per stderr line. Console output uses the same event fields with human-readable formatting. JSON key order is not part of the contract.

The configured chain filters built-in fields after merging logging context. Application-bound state, tenant data, credentials, command and run_id are excluded from built-in events. Generic application events retain context merging and existing redaction. Applications supplying their own structlog processors own any additional data those processors inject.

See configuration and diagnostic disclosure.

HTTP terminal event

http.call emits once per logical sync or async judgment call, at debug level. Retries do not add events. The event records the final attempt's status. A recovered rate limit followed by success records status 200 and success. A final transport failure records null status and error. Parsing failure records the received status and error. Cancellation propagates and records error.

With judgevet's configured JSON renderer, every event has exactly these keys:

Key Type Meaning
event string Always http.call.
level string Always debug.
timestamp string UTC ISO timestamp added by the renderer.
model string or null Filtered requested model, including the adapter default.
question_count integer Number of supplied questions.
status_code integer or null Final attempt's HTTP status; null when no response arrived.
outcome string success after typed parsing; otherwise error.
resolved_model string or null Filtered model from the successful typed response; null on failure.
input_tokens integer or null Typed input token count; null when unavailable.
output_tokens integer or null Typed output token count; null when unavailable.
request_id string or null Dedicated caller correlation binding; null outside a scope.

Token counts come only from a successful typed response. The current HTTP parser requires both counts; missing usage is a parsing failure, not estimated usage. The logger never computes a total or reads counts from a failed response.

The model filter retains jev-latest and jev-<digits>.<digits>.<digits> values of at most 64 characters. Other model strings become null in diagnostics only. Actual request and response model values remain unchanged. This filter does not validate upstream model availability. Version-shaped identifiers must still contain no sensitive data.

Question identifiers, instructions, criteria, state, answers, headers and exception text are excluded. Question identifiers can contain customer data; this contract deliberately provides only the question count.

MCP runtime event

mcp.runtime forwards SDK diagnostics at warning, error or critical severity. Its exact keys are event, level, timestamp and request_id. The event name is fixed. Correlation comes from the dedicated binding in the emitting context. SDK messages, arguments and tracebacks are excluded.

MCP JSON-RPC errors on stdout are separate from diagnostic stderr. This event contract does not scrub protocol error content or arbitrary library exceptions.

Caller correlation

Import bind_request_id from judgevet. Use its context manager around a call, including inside an async function. The complete synchronous example binds a caller identifier and configures logging explicitly.

A non-sensitive opaque identifier must match ASCII [A-Za-z0-9][A-Za-z0-9._:-]{0,127}. Invalid types, characters or lengths raise ValueError on scope entry without changing the outer binding. Syntax validation cannot determine whether an identifier contains a secret; the caller owns that choice. The error message does not echo the supplied value.

Nested scopes restore the previous identifier. Passing None temporarily clears it. Normal exit, exceptions and task cancellation all restore the previous value. Concurrent asyncio tasks have separate bindings. A child inherits its creation context; a later parent change does not rewrite the child's context. Unrelated threads do not receive automatic propagation. These semantics follow Python context variables.

Correlation does not configure logging, generate an identifier, alter port signatures. It stays out of HTTP headers by default. The CLI has no request-ID flag, and MCP tool schemas have no request-ID field. Explicit GatewayConfig(request_id_header=...) opts into header propagation; see gateway configuration. Do not use arbitrary structlog context fields to supply this dedicated binding. See structlog context behavior for application-owned logging context.