Core implementation of the v2 LLM dashboard validation pipeline: - LLM plugin with Path A (screenshots) and Path B (logs-only) execution - Validation task management (CRUD, schedule, run) - WebSocket task progress with Python 3.13 asyncio fix - Cross-task runs listing (GET /validation-tasks/runs/all) - RecordResponse schema for validation records - JSON prompt helper, per-dashboard status aggregation - Prompt templates with docs/git-commit/validation presets - Migration: v2 validation models + description column - Tests: plugin persistence, prompt templates, batch, payload, url
237 lines
7.9 KiB
Python
237 lines
7.9 KiB
Python
# #region CotLoggerModule [C:4] [TYPE Module] [SEMANTICS logging, cot, json, trace, structured]
|
|
# @BRIEF Structured JSON logger implementing the decision audit logging protocol.
|
|
# Uses ContextVar for trace_id and span_id propagation across async contexts.
|
|
# Provides log(), MarkerLogger, seed_trace_id(), push_span(), pop_span().
|
|
# @LAYER Core
|
|
# @RELATION CALLED_BY -> [TraceContextMiddleware]
|
|
# @RELATION CALLED_BY -> [CotLoggerModule]
|
|
# @PRE Python 3.7+ (ContextVar available).
|
|
# @POST JSON log records written to the 'cot' logger at appropriate levels.
|
|
# @SIDE_EFFECT Writes structured JSON to the 'cot' Python logger.
|
|
# @DATA_CONTRACT Log call -> Single-line JSON to logging.StreamHandler/file.
|
|
|
|
from contextvars import ContextVar
|
|
import logging
|
|
from typing import Any
|
|
import uuid
|
|
|
|
# #region cot_trace_context [C:1] [TYPE Data] [SEMANTICS contextvar, trace_id, span_id, propagation]
|
|
# @BRIEF ContextVars for trace ID and span ID propagation across async boundaries.
|
|
_trace_id: ContextVar[str] = ContextVar("_trace_id", default="")
|
|
_span_id: ContextVar[str] = ContextVar("_span_id", default="")
|
|
# #endregion cot_trace_context
|
|
|
|
# #region cot_logger_instance [C:1] [TYPE Data] [SEMANTICS logger, instance]
|
|
# @BRIEF Dedicated Python logger for all decision audit log output.
|
|
cot_logger = logging.getLogger("cot")
|
|
# #endregion cot_logger_instance
|
|
|
|
__all__ = [
|
|
"MarkerLogger",
|
|
"get_trace_id",
|
|
"log",
|
|
"pop_span",
|
|
"push_span",
|
|
"seed_trace_id",
|
|
"set_trace_id",
|
|
]
|
|
|
|
# #region seed_trace_id [C:1] [TYPE Function] [SEMANTICS trace_id, uuid, contextvar, set]
|
|
# @BRIEF Generate a new UUID4 trace_id, set it in ContextVar, and return it.
|
|
def seed_trace_id() -> str:
|
|
"""Generate a new UUID4 trace ID and store it in the thread-local ContextVar.
|
|
|
|
Returns:
|
|
str: The newly generated trace ID.
|
|
"""
|
|
trace_id = str(uuid.uuid4())
|
|
_trace_id.set(trace_id)
|
|
return trace_id
|
|
|
|
|
|
# #endregion seed_trace_id
|
|
|
|
# #region set_trace_id [C:1] [TYPE Function] [SEMANTICS trace_id, contextvar, set, public]
|
|
# @BRIEF Set an explicit trace_id into the ContextVar (e.g. from an incoming header).
|
|
def set_trace_id(trace_id: str) -> None:
|
|
"""Set a specific trace_id into the ContextVar.
|
|
|
|
This is used by TraceContextMiddleware when an ``X-Trace-ID`` header
|
|
is present, enabling cross-service trace chaining.
|
|
|
|
Args:
|
|
trace_id: The trace ID to set.
|
|
"""
|
|
_trace_id.set(trace_id)
|
|
|
|
|
|
# #endregion set_trace_id
|
|
|
|
# #region get_trace_id [C:1] [TYPE Function] [SEMANTICS trace_id, contextvar, get, public]
|
|
# @BRIEF Get the current trace_id from ContextVar.
|
|
def get_trace_id() -> str:
|
|
"""Get the current trace_id from the thread-local ContextVar.
|
|
|
|
Returns:
|
|
str: The current trace ID, or empty string if none set.
|
|
"""
|
|
return _trace_id.get()
|
|
|
|
|
|
# #endregion get_trace_id
|
|
|
|
# #region push_span [C:1] [TYPE Function] [SEMANTICS span_id, contextvar, stack]
|
|
# @BRIEF Set a new span_id in ContextVar and return the previous span_id for later restoration.
|
|
def push_span(span: str) -> str:
|
|
"""Push a new span ID onto the context and return the previous span ID.
|
|
|
|
Args:
|
|
span: The new span identifier (e.g. function or operation name).
|
|
|
|
Returns:
|
|
str: The previous span ID, suitable for passing to pop_span().
|
|
"""
|
|
prev = _span_id.get()
|
|
_span_id.set(span)
|
|
return prev
|
|
|
|
|
|
# #endregion push_span
|
|
|
|
# #region pop_span [C:1] [TYPE Function] [SEMANTICS span_id, contextvar, restore]
|
|
# @BRIEF Restore a previous span_id into the ContextVar.
|
|
def pop_span(prev: str) -> None:
|
|
"""Restore a previous span ID into the ContextVar.
|
|
|
|
Args:
|
|
prev: The span ID to restore (previously returned by push_span).
|
|
"""
|
|
_span_id.set(prev)
|
|
|
|
|
|
# #endregion pop_span
|
|
|
|
# #region cot_log_function [C:2] [TYPE Function] [SEMANTICS log, json, structured, marker]
|
|
# @BRIEF Core structured logging function that emits a single-line JSON record.
|
|
def log(
|
|
src: str,
|
|
marker: str,
|
|
intent: str,
|
|
payload: dict[str, Any] | None = None,
|
|
error: str | None = None,
|
|
level: str | None = None,
|
|
) -> None:
|
|
"""Emit a single-line structured JSON log record through the 'cot' logger.
|
|
|
|
Args:
|
|
src: Qualified function or component name (e.g. 'AuthRepository.get_user').
|
|
marker: Protocol marker — one of 'REASON', 'REFLECT', 'EXPLORE'.
|
|
intent: Short one-line description of the intent or action.
|
|
payload: Optional structured data dict to include in the record.
|
|
error: Required for EXPLORE markers; describes the violated assumption.
|
|
level: Log level override. Auto-inferred from marker if omitted
|
|
(REASON/REFLECT -> INFO, EXPLORE -> WARNING).
|
|
|
|
Side effects:
|
|
Writes a single-line JSON string to the 'cot' Python logger.
|
|
"""
|
|
if level is None:
|
|
level = "WARNING" if marker == "EXPLORE" else "INFO"
|
|
|
|
# Build structured extra data for the LogRecord.
|
|
# CotJsonFormatter will read these attributes to produce the final JSON output,
|
|
# consistent with the main superset_tools_app logger.
|
|
extra: dict[str, Any] = {
|
|
"marker": marker,
|
|
"intent": intent,
|
|
"src": src,
|
|
}
|
|
|
|
if payload is not None:
|
|
extra["payload"] = payload
|
|
|
|
if error is not None:
|
|
extra["error"] = error
|
|
|
|
log_func = {
|
|
"WARNING": cot_logger.warning,
|
|
"ERROR": cot_logger.error,
|
|
"DEBUG": cot_logger.debug,
|
|
}.get(level, cot_logger.info)
|
|
|
|
log_func(intent, extra=extra)
|
|
|
|
|
|
# #endregion cot_log_function
|
|
|
|
# #region MarkerLogger [C:2] [TYPE Class] [SEMANTICS logger, proxy, marker, syntactic-sugar]
|
|
# @BRIEF Thin proxy class providing .reason(), .reflect(), .explore() convenience methods.
|
|
class MarkerLogger:
|
|
"""Thin proxy over the cot_logger.log() function.
|
|
|
|
Usage::
|
|
|
|
log = MarkerLogger("AuthService")
|
|
log.reason("Validating credentials", payload={"user_id": "..."})
|
|
log.reflect("Validation complete", payload={"result": "ok"})
|
|
log.explore("Token expired", error="JWT expired, attempting refresh")
|
|
|
|
Each method delegates to :func:`log` with the corresponding marker.
|
|
"""
|
|
|
|
# #region MarkerLogger.__init__ [TYPE Function]
|
|
# @BRIEF Store the module/component name used as the 'src' field in all log calls.
|
|
def __init__(self, module_name: str) -> None:
|
|
"""Initialise the MarkerLogger with a module or component name.
|
|
|
|
Args:
|
|
module_name: The value used for the 'src' field (e.g. 'AuthService').
|
|
"""
|
|
self._module_name = module_name
|
|
|
|
# #endregion MarkerLogger.__init__
|
|
|
|
# #region MarkerLogger.reason [TYPE Function]
|
|
# @BRIEF Log a REASON marker — strict deduction, core logic.
|
|
def reason(
|
|
self, intent: str, *, payload: dict[str, Any] | None = None
|
|
) -> None:
|
|
"""Log a REASON marker (INFO level)."""
|
|
log(self._module_name, "REASON", intent, payload=payload)
|
|
|
|
# #endregion MarkerLogger.reason
|
|
|
|
# #region MarkerLogger.reflect [TYPE Function]
|
|
# @BRIEF Log a REFLECT marker — self-check, structural validation.
|
|
def reflect(
|
|
self, intent: str, *, payload: dict[str, Any] | None = None
|
|
) -> None:
|
|
"""Log a REFLECT marker (INFO level)."""
|
|
log(self._module_name, "REFLECT", intent, payload=payload)
|
|
|
|
# #endregion MarkerLogger.reflect
|
|
|
|
# #region MarkerLogger.explore [TYPE Function]
|
|
# @BRIEF Log an EXPLORE marker — searching, alternatives, violated assumptions.
|
|
def explore(
|
|
self,
|
|
intent: str,
|
|
*,
|
|
payload: dict[str, Any] | None = None,
|
|
error: str | None = None,
|
|
) -> None:
|
|
"""Log an EXPLORE marker (WARNING level).
|
|
|
|
Args:
|
|
intent: Short description of what was being attempted.
|
|
payload: Optional structured data.
|
|
error: Describes the violated assumption or error context.
|
|
"""
|
|
log(self._module_name, "EXPLORE", intent, payload=payload, error=error)
|
|
|
|
# #endregion MarkerLogger.explore
|
|
|
|
|
|
# #endregion MarkerLogger
|
|
# #endregion CotLoggerModule
|