3 Commits

Author SHA1 Message Date
cb7386f274 superset_tool logger rework 2025-12-27 05:53:30 +03:00
83e34e1799 feat(logging): implement configurable belief state logging
- Add LoggingConfig model and logging field to GlobalSettings
- Implement belief_scope context manager for structured logging
- Add configure_logger for dynamic level and file rotation settings
- Add logging configuration UI to Settings page
- Update ConfigManager to apply logging settings on initialization and updates
2025-12-27 05:39:33 +03:00
d197303b9f 006 plan ready 2025-12-26 19:36:49 +03:00
25 changed files with 992 additions and 64 deletions

1
.gitignore vendored
View File

@@ -61,3 +61,4 @@ keyring passwords.py
*git* *git*
*tech_spec* *tech_spec*
dashboards dashboards
backend/mappings.db

View File

@@ -29,9 +29,9 @@ cd src; pytest; ruff check .
Python 3.9+ (Backend), Node.js 18+ (Frontend Build): Follow standard conventions Python 3.9+ (Backend), Node.js 18+ (Frontend Build): Follow standard conventions
## Recent Changes ## Recent Changes
- 006-configurable-belief-logs: Added Python 3.9+ + FastAPI (Backend), Pydantic (Config), Svelte (Frontend)
- 005-fix-ui-ws-validation: Added Python 3.9+ (Backend), Node.js 18+ (Frontend Build) - 005-fix-ui-ws-validation: Added Python 3.9+ (Backend), Node.js 18+ (Frontend Build)
- 005-fix-ui-ws-validation: Added Python 3.9+, Node.js 18+ + FastAPI, SvelteKit, Tailwind CSS, Pydantic - 005-fix-ui-ws-validation: Added Python 3.9+, Node.js 18+ + FastAPI, SvelteKit, Tailwind CSS, Pydantic
- 005-fix-ui-ws-validation: Added Python 3.9+, Node.js 18+ + FastAPI, SvelteKit, Tailwind CSS, Pydantic
<!-- MANUAL ADDITIONS START --> <!-- MANUAL ADDITIONS START -->

View File

@@ -1,29 +1,68 @@
# ss-tools Constitution <!--
SYNC IMPACT REPORT
Version: 1.1.0 (Svelte Support)
Changes:
- Added Svelte Component semantic markup standards.
- Updated File Structure Standards to include `.svelte` files.
- Refined File Structure Standards to distinguish between Python Modules and Svelte Components.
Templates Status:
- .specify/templates/plan-template.md: ⚠ Pending (Needs update to include Component headers in checks).
- .specify/templates/spec-template.md: ✅ Aligned.
- .specify/templates/tasks-template.md: ⚠ Pending (Needs update to include Component definition tasks).
-->
# Semantic Code Generation Constitution
## Core Principles ## Core Principles
### I. SPA-First Architecture ### I. Causal Validity (Contracts First)
The frontend MUST be a Static Single Page Application (SPA) served by the Python backend. No Node.js server is permitted in production. The backend serves the `index.html` entry point for all non-API routes. Semantic definitions (Contracts) must ALWAYS precede implementation code. Logic is downstream of definition. We define the structure and constraints (`[DEF]`, `@PRE`, `@POST`) before writing the executable logic. This ensures that the "what" and "why" govern the "how".
### II. API-Driven Communication ### II. Immutability of Architecture
All data retrieval and state changes MUST be performed via the backend REST API or WebSockets. The frontend should not access the database or filesystem directly. Once defined, architectural decisions in the Module Header (`@LAYER`, `@INVARIANT`, `@CONSTRAINT`) are treated as immutable constraints for that module. Changes to these require an explicit refactoring step, not ad-hoc modification during implementation.
### III. Modern Stack Consistency ### III. Semantic Format Compliance
The project strictly uses SvelteKit (Frontend), FastAPI (Backend), and Tailwind CSS (Styling). New dependencies must be justified and approved. All output must strictly follow the `[DEF]` / `[/DEF]` anchor syntax with specific Metadata Tags (`@KEY`) and Graph Relations (`@RELATION`). This structure is non-negotiable as it ensures the codebase remains machine-readable, fractal-structured, and optimized for Sparse Attention navigation by AI agents.
### IV. Semantic Protocol Adherence (GRACE-Poly) ### IV. Design by Contract (DbC)
All code generation and modification MUST adhere to the Semantic Protocol defined in `semantic_protocol.md`. Contracts are the Source of Truth. Functions and Classes must define their purpose, specifications, and constraints (`@PRE`, `@POST`, `@THROW`) in the metadata block before implementation. Implementation must strictly satisfy these contracts.
- **Anchors**: Use `[DEF:id:Type]` and `[/DEF:id]` to define semantic boundaries.
- **Contracts**: Define `@PRE` and `@POST` conditions in headers. ### V. Belief State Logging
- **Logging**: Use structured logging with `[AnchorID][State]` format. Logs must define the agent's internal state for debugging and coherence checks. We use a strict format: `logger.level(f"[{ANCHOR_ID}][{STATE}] {MESSAGE} context={...}")` to track transitions between `Entry`, `Validation`, `Action`, and `Coherence` states.
- **Immutability**: Respect architectural decisions in headers.
## File Structure Standards
### Python Modules
Every `.py` file must start with a Module definition header (`[DEF:module_name:Module]`) containing:
- `@SEMANTICS`: Keywords for vector search.
- `@PURPOSE`: Primary responsibility of the module.
- `@LAYER`: Architecture layer (Domain/Infra/UI).
- `@RELATION`: Dependencies.
- `@INVARIANT` & `@CONSTRAINT`: Immutable rules.
- `@PUBLIC_API`: Exported symbols.
### Svelte Components
Every `.svelte` file must start with a Component definition header (`[DEF:ComponentName:Component]`) wrapped in an HTML comment `<!-- ... -->` containing:
- `@SEMANTICS`: Keywords for vector search.
- `@PURPOSE`: Primary responsibility of the component.
- `@LAYER`: Architecture layer (UI/State/Layout).
- `@RELATION`: Child components, Stores used, API calls.
- `@PROPS`: Input properties.
- `@EVENTS`: Emitted events.
- `@INVARIANT`: Immutable UI/State rules.
## Generation Workflow
The development process follows a strict sequence:
1. **Analyze Request**: Identify target module and graph position.
2. **Define Structure**: Generate `[DEF]` anchors and Contracts FIRST.
3. **Implement Logic**: Write code satisfying Contracts.
4. **Validate**: If logic conflicts with Contract -> Stop -> Report Error.
## Governance ## Governance
This Constitution establishes the "Semantic Code Generation Protocol" as the supreme law of this repository.
### Compliance - **Automated Enforcement**: All code generation tools and agents must parse and validate adherence to the `[DEF]` syntax and Contract requirements.
All Pull Requests and code modifications must be verified against this Constitution. Violations of Core Principles are considered critical defects. - **Amendments**: Changes to the syntax or core principles require a formal amendment to this Constitution and a corresponding update to the constitution
- **Review**: Code reviews must verify that implementation matches the preceding contracts and that no "naked code" exists outside of semantic anchors.
- **Compliance**: Failure to adhere to the `[DEF]` / `[/DEF]` structure constitutes a build failure.
### Amendments **Version**: 1.1.0 | **Ratified**: 2025-12-19 | **Last Amended**: 2025-12-19
Changes to this Constitution require a formal RFC process and approval from the project lead.
**Version**: 1.0.0 | **Ratified**: 2025-12-20

Binary file not shown.

View File

@@ -15,6 +15,7 @@ from backend.src.dependencies import get_config_manager
from backend.src.core.superset_client import SupersetClient from backend.src.core.superset_client import SupersetClient
from superset_tool.models import SupersetConfig from superset_tool.models import SupersetConfig
from pydantic import BaseModel from pydantic import BaseModel
from backend.src.core.logger import logger
# [/SECTION] # [/SECTION]
router = APIRouter(prefix="/api/environments", tags=["environments"]) router = APIRouter(prefix="/api/environments", tags=["environments"])
@@ -38,7 +39,9 @@ class DatabaseResponse(BaseModel):
# @RETURN: List[EnvironmentResponse] # @RETURN: List[EnvironmentResponse]
@router.get("", response_model=List[EnvironmentResponse]) @router.get("", response_model=List[EnvironmentResponse])
async def get_environments(config_manager=Depends(get_config_manager)): async def get_environments(config_manager=Depends(get_config_manager)):
logger.info(f"[get_environments][Debug] Config path: {config_manager.config_path}")
envs = config_manager.get_environments() envs = config_manager.get_environments()
logger.info(f"[get_environments][Debug] Found {len(envs)} environments")
return [EnvironmentResponse(id=e.id, name=e.name, url=e.url) for e in envs] return [EnvironmentResponse(id=e.id, name=e.name, url=e.url) for e in envs]
# [/DEF:get_environments] # [/DEF:get_environments]

View File

@@ -16,7 +16,7 @@ from ...core.config_models import AppConfig, Environment, GlobalSettings
from ...dependencies import get_config_manager from ...dependencies import get_config_manager
from ...core.config_manager import ConfigManager from ...core.config_manager import ConfigManager
from ...core.logger import logger from ...core.logger import logger
from superset_tool.client import SupersetClient from ...core.superset_client import SupersetClient
from superset_tool.models import SupersetConfig from superset_tool.models import SupersetConfig
import os import os
# [/SECTION] # [/SECTION]

View File

@@ -16,7 +16,7 @@ import os
from pathlib import Path from pathlib import Path
from typing import Optional, List from typing import Optional, List
from .config_models import AppConfig, Environment, GlobalSettings from .config_models import AppConfig, Environment, GlobalSettings
from .logger import logger from .logger import logger, configure_logger
# [/SECTION] # [/SECTION]
# [DEF:ConfigManager:Class] # [DEF:ConfigManager:Class]
@@ -39,6 +39,9 @@ class ConfigManager:
self.config_path = Path(config_path) self.config_path = Path(config_path)
self.config: AppConfig = self._load_config() self.config: AppConfig = self._load_config()
# Configure logger with loaded settings
configure_logger(self.config.settings.logging)
# 3. Runtime check of @POST # 3. Runtime check of @POST
assert isinstance(self.config, AppConfig), "self.config must be an instance of AppConfig" assert isinstance(self.config, AppConfig), "self.config must be an instance of AppConfig"
@@ -121,6 +124,9 @@ class ConfigManager:
self.config.settings = settings self.config.settings = settings
self.save() self.save()
# Reconfigure logger with new settings
configure_logger(settings.logging)
logger.info(f"[update_global_settings][Exit] Settings updated") logger.info(f"[update_global_settings][Exit] Settings updated")
# [/DEF:update_global_settings] # [/DEF:update_global_settings]

View File

@@ -19,11 +19,22 @@ class Environment(BaseModel):
is_default: bool = False is_default: bool = False
# [/DEF:Environment] # [/DEF:Environment]
# [DEF:LoggingConfig:DataClass]
# @PURPOSE: Defines the configuration for the application's logging system.
class LoggingConfig(BaseModel):
level: str = "INFO"
file_path: Optional[str] = "logs/app.log"
max_bytes: int = 10 * 1024 * 1024
backup_count: int = 5
enable_belief_state: bool = True
# [/DEF:LoggingConfig]
# [DEF:GlobalSettings:DataClass] # [DEF:GlobalSettings:DataClass]
# @PURPOSE: Represents global application settings. # @PURPOSE: Represents global application settings.
class GlobalSettings(BaseModel): class GlobalSettings(BaseModel):
backup_path: str backup_path: str
default_environment_id: Optional[str] = None default_environment_id: Optional[str] = None
logging: LoggingConfig = Field(default_factory=LoggingConfig)
# [/DEF:GlobalSettings] # [/DEF:GlobalSettings]
# [DEF:AppConfig:DataClass] # [DEF:AppConfig:DataClass]

View File

@@ -4,12 +4,32 @@
# @LAYER: Core # @LAYER: Core
# @RELATION: Used by the main application and other modules to log events. The WebSocketLogHandler is used by the WebSocket endpoint in app.py. # @RELATION: Used by the main application and other modules to log events. The WebSocketLogHandler is used by the WebSocket endpoint in app.py.
import logging import logging
import threading
from datetime import datetime from datetime import datetime
from typing import Dict, Any, List, Optional from typing import Dict, Any, List, Optional
from collections import deque from collections import deque
from contextlib import contextmanager
from logging.handlers import RotatingFileHandler
from pydantic import BaseModel, Field from pydantic import BaseModel, Field
# Thread-local storage for belief state
_belief_state = threading.local()
# Global flag for belief state logging
_enable_belief_state = True
# [DEF:BeliefFormatter:Class]
# @PURPOSE: Custom logging formatter that adds belief state prefixes to log messages.
class BeliefFormatter(logging.Formatter):
def format(self, record):
msg = super().format(record)
anchor_id = getattr(_belief_state, 'anchor_id', None)
if anchor_id:
msg = f"[{anchor_id}][Action] {msg}"
return msg
# [/DEF:BeliefFormatter]
# Re-using LogEntry from task_manager for consistency # Re-using LogEntry from task_manager for consistency
# [DEF:LogEntry:Class] # [DEF:LogEntry:Class]
# @SEMANTICS: log, entry, record, pydantic # @SEMANTICS: log, entry, record, pydantic
@@ -22,6 +42,81 @@ class LogEntry(BaseModel):
# [/DEF] # [/DEF]
# [DEF:BeliefScope:Function]
# @PURPOSE: Context manager for structured Belief State logging.
@contextmanager
def belief_scope(anchor_id: str, message: str = ""):
# Log Entry if enabled
if _enable_belief_state:
entry_msg = f"[{anchor_id}][Entry]"
if message:
entry_msg += f" {message}"
logger.info(entry_msg)
# Set thread-local anchor_id
old_anchor = getattr(_belief_state, 'anchor_id', None)
_belief_state.anchor_id = anchor_id
try:
yield
# Log Coherence OK and Exit
logger.info(f"[{anchor_id}][Coherence:OK]")
if _enable_belief_state:
logger.info(f"[{anchor_id}][Exit]")
except Exception as e:
# Log Coherence Failed
logger.info(f"[{anchor_id}][Coherence:Failed] {str(e)}")
raise
finally:
# Restore old anchor
_belief_state.anchor_id = old_anchor
# [/DEF:BeliefScope]
# [DEF:ConfigureLogger:Function]
# @PURPOSE: Configures the logger with the provided logging settings.
# @PRE: config is a valid LoggingConfig instance.
# @POST: Logger level, handlers, and belief state flag are updated.
# @PARAM: config (LoggingConfig) - The logging configuration.
def configure_logger(config):
global _enable_belief_state
_enable_belief_state = config.enable_belief_state
# Set logger level
level = getattr(logging, config.level.upper(), logging.INFO)
logger.setLevel(level)
# Remove existing file handlers
handlers_to_remove = [h for h in logger.handlers if isinstance(h, RotatingFileHandler)]
for h in handlers_to_remove:
logger.removeHandler(h)
h.close()
# Add file handler if file_path is set
if config.file_path:
import os
from pathlib import Path
log_file = Path(config.file_path)
log_file.parent.mkdir(parents=True, exist_ok=True)
file_handler = RotatingFileHandler(
config.file_path,
maxBytes=config.max_bytes,
backupCount=config.backup_count
)
file_handler.setFormatter(BeliefFormatter(
'[%(asctime)s][%(levelname)s][%(name)s] %(message)s'
))
logger.addHandler(file_handler)
# Update existing handlers' formatters to BeliefFormatter
for handler in logger.handlers:
if not isinstance(handler, RotatingFileHandler):
handler.setFormatter(BeliefFormatter(
'[%(asctime)s][%(levelname)s][%(name)s] %(message)s'
))
# [/DEF:ConfigureLogger]
# [DEF:WebSocketLogHandler:Class] # [DEF:WebSocketLogHandler:Class]
# @SEMANTICS: logging, handler, websocket, buffer # @SEMANTICS: logging, handler, websocket, buffer
# @PURPOSE: A custom logging handler that captures log records into a buffer. It is designed to be extended for real-time log streaming over WebSockets. # @PURPOSE: A custom logging handler that captures log records into a buffer. It is designed to be extended for real-time log streaming over WebSockets.
@@ -72,7 +167,7 @@ logger = logging.getLogger("superset_tools_app")
logger.setLevel(logging.INFO) logger.setLevel(logging.INFO)
# Create a formatter # Create a formatter
formatter = logging.Formatter( formatter = BeliefFormatter(
'[%(asctime)s][%(levelname)s][%(name)s] %(message)s' '[%(asctime)s][%(levelname)s][%(name)s] %(message)s'
) )

View File

@@ -11,11 +11,18 @@
from typing import List, Dict, Optional, Tuple from typing import List, Dict, Optional, Tuple
from superset_tool.client import SupersetClient as BaseSupersetClient from superset_tool.client import SupersetClient as BaseSupersetClient
from superset_tool.models import SupersetConfig from superset_tool.models import SupersetConfig
from backend.src.core.logger import logger
from superset_tool.utils.logger import SupersetLogger
# [/SECTION] # [/SECTION]
# [DEF:SupersetClient:Class] # [DEF:SupersetClient:Class]
# @PURPOSE: Extended SupersetClient for migration-specific operations. # @PURPOSE: Extended SupersetClient for migration-specific operations.
class SupersetClient(BaseSupersetClient): class SupersetClient(BaseSupersetClient):
def __init__(self, config: SupersetConfig):
# Initialize with the application's logger wrapped in SupersetLogger
# to ensure BeliefFormatter is used.
sl_logger = SupersetLogger(logger=logger)
super().__init__(config=config, logger=sl_logger)
# [DEF:SupersetClient.get_databases_summary:Function] # [DEF:SupersetClient.get_databases_summary:Function]
# @PURPOSE: Fetch a summary of databases including uuid, name, and engine. # @PURPOSE: Fetch a summary of databases including uuid, name, and engine.

View File

@@ -0,0 +1,44 @@
import pytest
from backend.src.core.logger import belief_scope, logger
def test_belief_scope_logs_entry_action_exit(caplog):
"""Test that belief_scope generates [ID][Entry], [ID][Action], and [ID][Exit] logs."""
caplog.set_level("INFO")
with belief_scope("TestFunction"):
logger.info("Doing something important")
# Check that the logs contain the expected patterns
log_messages = [record.message for record in caplog.records]
assert any("[TestFunction][Entry]" in msg for msg in log_messages), "Entry log not found"
assert any("[TestFunction][Action] Doing something important" in msg for msg in log_messages), "Action log not found"
assert any("[TestFunction][Exit]" in msg for msg in log_messages), "Exit log not found"
def test_belief_scope_error_handling(caplog):
"""Test that belief_scope logs Coherence:Failed on exception."""
caplog.set_level("INFO")
with pytest.raises(ValueError):
with belief_scope("FailingFunction"):
raise ValueError("Something went wrong")
log_messages = [record.message for record in caplog.records]
assert any("[FailingFunction][Entry]" in msg for msg in log_messages), "Entry log not found"
assert any("[FailingFunction][Coherence:Failed]" in msg for msg in log_messages), "Failed coherence log not found"
# Exit should not be logged on failure
def test_belief_scope_success_coherence(caplog):
"""Test that belief_scope logs Coherence:OK on success."""
caplog.set_level("INFO")
with belief_scope("SuccessFunction"):
pass
log_messages = [record.message for record in caplog.records]
assert any("[SuccessFunction][Coherence:OK]" in msg for msg in log_messages), "Success coherence log not found"

View File

@@ -26,57 +26,85 @@
* ``` * ```
*/ */
declare module '$env/static/private' { declare module '$env/static/private' {
export const LESSOPEN: string;
export const USER: string; export const USER: string;
export const npm_config_user_agent: string; export const npm_config_user_agent: string;
export const XDG_SESSION_TYPE: string;
export const npm_node_execpath: string; export const npm_node_execpath: string;
export const SHLVL: string; export const SHLVL: string;
export const npm_config_noproxy: string; export const npm_config_noproxy: string;
export const LESS: string;
export const HOME: string; export const HOME: string;
export const OLDPWD: string; export const OLDPWD: string;
export const DESKTOP_SESSION: string;
export const npm_package_json: string; export const npm_package_json: string;
export const LSCOLORS: string;
export const ZSH: string;
export const GNOME_SHELL_SESSION_MODE: string;
export const GTK_MODULES: string;
export const PAGER: string;
export const PS1: string; export const PS1: string;
export const npm_config_userconfig: string; export const npm_config_userconfig: string;
export const npm_config_local_prefix: string; export const npm_config_local_prefix: string;
export const SYSTEMD_EXEC_PID: string;
export const DBUS_SESSION_BUS_ADDRESS: string; export const DBUS_SESSION_BUS_ADDRESS: string;
export const WSL_DISTRO_NAME: string; export const COLORTERM: string;
export const COLOR: string; export const COLOR: string;
export const npm_config_metrics_registry: string;
export const WAYLAND_DISPLAY: string; export const WAYLAND_DISPLAY: string;
export const LOGNAME: string; export const LOGNAME: string;
export const NAME: string; export const SDKMAN_CANDIDATES_API: string;
export const WSL_INTEROP: string;
export const PULSE_SERVER: string;
export const _: string; export const _: string;
export const npm_config_prefix: string; export const npm_config_prefix: string;
export const npm_config_npm_version: string; export const MEMORY_PRESSURE_WATCH: string;
export const XDG_SESSION_CLASS: string;
export const USERNAME: string;
export const TERM: string; export const TERM: string;
export const npm_config_cache: string; export const npm_config_cache: string;
export const GNOME_DESKTOP_SESSION_ID: string;
export const npm_config_node_gyp: string; export const npm_config_node_gyp: string;
export const PATH: string; export const PATH: string;
export const SDKMAN_CANDIDATES_DIR: string;
export const NODE: string; export const NODE: string;
export const npm_package_name: string; export const npm_package_name: string;
export const XDG_MENU_PREFIX: string;
export const SDKMAN_BROKER_API: string;
export const GNOME_TERMINAL_SCREEN: string;
export const GNOME_SETUP_DISPLAY: string;
export const XDG_RUNTIME_DIR: string; export const XDG_RUNTIME_DIR: string;
export const DISPLAY: string; export const DISPLAY: string;
export const LANG: string; export const LANG: string;
export const XDG_CURRENT_DESKTOP: string;
export const VIRTUAL_ENV_PROMPT: string; export const VIRTUAL_ENV_PROMPT: string;
export const XMODIFIERS: string;
export const XDG_SESSION_DESKTOP: string;
export const XAUTHORITY: string;
export const LS_COLORS: string; export const LS_COLORS: string;
export const GNOME_TERMINAL_SERVICE: string;
export const SDKMAN_DIR: string;
export const SDKMAN_PLATFORM: string;
export const npm_lifecycle_script: string; export const npm_lifecycle_script: string;
export const SSH_AUTH_SOCK: string;
export const SHELL: string; export const SHELL: string;
export const npm_package_version: string; export const npm_package_version: string;
export const npm_lifecycle_event: string; export const npm_lifecycle_event: string;
export const QT_ACCESSIBILITY: string;
export const GDMSESSION: string;
export const GOOGLE_CLOUD_PROJECT: string; export const GOOGLE_CLOUD_PROJECT: string;
export const LESSCLOSE: string; export const GPG_AGENT_INFO: string;
export const VIRTUAL_ENV: string; export const VIRTUAL_ENV: string;
export const QT_IM_MODULE: string;
export const npm_config_globalconfig: string; export const npm_config_globalconfig: string;
export const npm_config_init_module: string; export const npm_config_init_module: string;
export const JAVA_HOME: string;
export const PWD: string; export const PWD: string;
export const npm_config_globalignorefile: string;
export const npm_execpath: string; export const npm_execpath: string;
export const XDG_DATA_DIRS: string; export const XDG_DATA_DIRS: string;
export const npm_config_global_prefix: string; export const npm_config_global_prefix: string;
export const npm_command: string; export const npm_command: string;
export const WSL2_GUI_APPS_ENABLED: string; export const QT_IM_MODULES: string;
export const HOSTTYPE: string; export const MEMORY_PRESSURE_WRITE: string;
export const WSLENV: string; export const VTE_VERSION: string;
export const INIT_CWD: string; export const INIT_CWD: string;
export const EDITOR: string; export const EDITOR: string;
export const NODE_ENV: string; export const NODE_ENV: string;
@@ -109,57 +137,85 @@ declare module '$env/static/public' {
*/ */
declare module '$env/dynamic/private' { declare module '$env/dynamic/private' {
export const env: { export const env: {
LESSOPEN: string;
USER: string; USER: string;
npm_config_user_agent: string; npm_config_user_agent: string;
XDG_SESSION_TYPE: string;
npm_node_execpath: string; npm_node_execpath: string;
SHLVL: string; SHLVL: string;
npm_config_noproxy: string; npm_config_noproxy: string;
LESS: string;
HOME: string; HOME: string;
OLDPWD: string; OLDPWD: string;
DESKTOP_SESSION: string;
npm_package_json: string; npm_package_json: string;
LSCOLORS: string;
ZSH: string;
GNOME_SHELL_SESSION_MODE: string;
GTK_MODULES: string;
PAGER: string;
PS1: string; PS1: string;
npm_config_userconfig: string; npm_config_userconfig: string;
npm_config_local_prefix: string; npm_config_local_prefix: string;
SYSTEMD_EXEC_PID: string;
DBUS_SESSION_BUS_ADDRESS: string; DBUS_SESSION_BUS_ADDRESS: string;
WSL_DISTRO_NAME: string; COLORTERM: string;
COLOR: string; COLOR: string;
npm_config_metrics_registry: string;
WAYLAND_DISPLAY: string; WAYLAND_DISPLAY: string;
LOGNAME: string; LOGNAME: string;
NAME: string; SDKMAN_CANDIDATES_API: string;
WSL_INTEROP: string;
PULSE_SERVER: string;
_: string; _: string;
npm_config_prefix: string; npm_config_prefix: string;
npm_config_npm_version: string; MEMORY_PRESSURE_WATCH: string;
XDG_SESSION_CLASS: string;
USERNAME: string;
TERM: string; TERM: string;
npm_config_cache: string; npm_config_cache: string;
GNOME_DESKTOP_SESSION_ID: string;
npm_config_node_gyp: string; npm_config_node_gyp: string;
PATH: string; PATH: string;
SDKMAN_CANDIDATES_DIR: string;
NODE: string; NODE: string;
npm_package_name: string; npm_package_name: string;
XDG_MENU_PREFIX: string;
SDKMAN_BROKER_API: string;
GNOME_TERMINAL_SCREEN: string;
GNOME_SETUP_DISPLAY: string;
XDG_RUNTIME_DIR: string; XDG_RUNTIME_DIR: string;
DISPLAY: string; DISPLAY: string;
LANG: string; LANG: string;
XDG_CURRENT_DESKTOP: string;
VIRTUAL_ENV_PROMPT: string; VIRTUAL_ENV_PROMPT: string;
XMODIFIERS: string;
XDG_SESSION_DESKTOP: string;
XAUTHORITY: string;
LS_COLORS: string; LS_COLORS: string;
GNOME_TERMINAL_SERVICE: string;
SDKMAN_DIR: string;
SDKMAN_PLATFORM: string;
npm_lifecycle_script: string; npm_lifecycle_script: string;
SSH_AUTH_SOCK: string;
SHELL: string; SHELL: string;
npm_package_version: string; npm_package_version: string;
npm_lifecycle_event: string; npm_lifecycle_event: string;
QT_ACCESSIBILITY: string;
GDMSESSION: string;
GOOGLE_CLOUD_PROJECT: string; GOOGLE_CLOUD_PROJECT: string;
LESSCLOSE: string; GPG_AGENT_INFO: string;
VIRTUAL_ENV: string; VIRTUAL_ENV: string;
QT_IM_MODULE: string;
npm_config_globalconfig: string; npm_config_globalconfig: string;
npm_config_init_module: string; npm_config_init_module: string;
JAVA_HOME: string;
PWD: string; PWD: string;
npm_config_globalignorefile: string;
npm_execpath: string; npm_execpath: string;
XDG_DATA_DIRS: string; XDG_DATA_DIRS: string;
npm_config_global_prefix: string; npm_config_global_prefix: string;
npm_command: string; npm_command: string;
WSL2_GUI_APPS_ENABLED: string; QT_IM_MODULES: string;
HOSTTYPE: string; MEMORY_PRESSURE_WRITE: string;
WSLENV: string; VTE_VERSION: string;
INIT_CWD: string; INIT_CWD: string;
EDITOR: string; EDITOR: string;
NODE_ENV: string; NODE_ENV: string;

View File

@@ -24,7 +24,7 @@ export const options = {
app: ({ head, body, assets, nonce, env }) => "<!DOCTYPE html>\n<html lang=\"en\">\n\t<head>\n\t\t<meta charset=\"utf-8\" />\n\t\t<link rel=\"icon\" href=\"" + assets + "/favicon.png\" />\n\t\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n\t\t" + head + "\n\t</head>\n\t<body data-sveltekit-preload-data=\"hover\">\n\t\t<div style=\"display: contents\">" + body + "</div>\n\t</body>\n</html>\n", app: ({ head, body, assets, nonce, env }) => "<!DOCTYPE html>\n<html lang=\"en\">\n\t<head>\n\t\t<meta charset=\"utf-8\" />\n\t\t<link rel=\"icon\" href=\"" + assets + "/favicon.png\" />\n\t\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n\t\t" + head + "\n\t</head>\n\t<body data-sveltekit-preload-data=\"hover\">\n\t\t<div style=\"display: contents\">" + body + "</div>\n\t</body>\n</html>\n",
error: ({ status, message }) => "<!doctype html>\n<html lang=\"en\">\n\t<head>\n\t\t<meta charset=\"utf-8\" />\n\t\t<title>" + message + "</title>\n\n\t\t<style>\n\t\t\tbody {\n\t\t\t\t--bg: white;\n\t\t\t\t--fg: #222;\n\t\t\t\t--divider: #ccc;\n\t\t\t\tbackground: var(--bg);\n\t\t\t\tcolor: var(--fg);\n\t\t\t\tfont-family:\n\t\t\t\t\tsystem-ui,\n\t\t\t\t\t-apple-system,\n\t\t\t\t\tBlinkMacSystemFont,\n\t\t\t\t\t'Segoe UI',\n\t\t\t\t\tRoboto,\n\t\t\t\t\tOxygen,\n\t\t\t\t\tUbuntu,\n\t\t\t\t\tCantarell,\n\t\t\t\t\t'Open Sans',\n\t\t\t\t\t'Helvetica Neue',\n\t\t\t\t\tsans-serif;\n\t\t\t\tdisplay: flex;\n\t\t\t\talign-items: center;\n\t\t\t\tjustify-content: center;\n\t\t\t\theight: 100vh;\n\t\t\t\tmargin: 0;\n\t\t\t}\n\n\t\t\t.error {\n\t\t\t\tdisplay: flex;\n\t\t\t\talign-items: center;\n\t\t\t\tmax-width: 32rem;\n\t\t\t\tmargin: 0 1rem;\n\t\t\t}\n\n\t\t\t.status {\n\t\t\t\tfont-weight: 200;\n\t\t\t\tfont-size: 3rem;\n\t\t\t\tline-height: 1;\n\t\t\t\tposition: relative;\n\t\t\t\ttop: -0.05rem;\n\t\t\t}\n\n\t\t\t.message {\n\t\t\t\tborder-left: 1px solid var(--divider);\n\t\t\t\tpadding: 0 0 0 1rem;\n\t\t\t\tmargin: 0 0 0 1rem;\n\t\t\t\tmin-height: 2.5rem;\n\t\t\t\tdisplay: flex;\n\t\t\t\talign-items: center;\n\t\t\t}\n\n\t\t\t.message h1 {\n\t\t\t\tfont-weight: 400;\n\t\t\t\tfont-size: 1em;\n\t\t\t\tmargin: 0;\n\t\t\t}\n\n\t\t\t@media (prefers-color-scheme: dark) {\n\t\t\t\tbody {\n\t\t\t\t\t--bg: #222;\n\t\t\t\t\t--fg: #ddd;\n\t\t\t\t\t--divider: #666;\n\t\t\t\t}\n\t\t\t}\n\t\t</style>\n\t</head>\n\t<body>\n\t\t<div class=\"error\">\n\t\t\t<span class=\"status\">" + status + "</span>\n\t\t\t<div class=\"message\">\n\t\t\t\t<h1>" + message + "</h1>\n\t\t\t</div>\n\t\t</div>\n\t</body>\n</html>\n" error: ({ status, message }) => "<!doctype html>\n<html lang=\"en\">\n\t<head>\n\t\t<meta charset=\"utf-8\" />\n\t\t<title>" + message + "</title>\n\n\t\t<style>\n\t\t\tbody {\n\t\t\t\t--bg: white;\n\t\t\t\t--fg: #222;\n\t\t\t\t--divider: #ccc;\n\t\t\t\tbackground: var(--bg);\n\t\t\t\tcolor: var(--fg);\n\t\t\t\tfont-family:\n\t\t\t\t\tsystem-ui,\n\t\t\t\t\t-apple-system,\n\t\t\t\t\tBlinkMacSystemFont,\n\t\t\t\t\t'Segoe UI',\n\t\t\t\t\tRoboto,\n\t\t\t\t\tOxygen,\n\t\t\t\t\tUbuntu,\n\t\t\t\t\tCantarell,\n\t\t\t\t\t'Open Sans',\n\t\t\t\t\t'Helvetica Neue',\n\t\t\t\t\tsans-serif;\n\t\t\t\tdisplay: flex;\n\t\t\t\talign-items: center;\n\t\t\t\tjustify-content: center;\n\t\t\t\theight: 100vh;\n\t\t\t\tmargin: 0;\n\t\t\t}\n\n\t\t\t.error {\n\t\t\t\tdisplay: flex;\n\t\t\t\talign-items: center;\n\t\t\t\tmax-width: 32rem;\n\t\t\t\tmargin: 0 1rem;\n\t\t\t}\n\n\t\t\t.status {\n\t\t\t\tfont-weight: 200;\n\t\t\t\tfont-size: 3rem;\n\t\t\t\tline-height: 1;\n\t\t\t\tposition: relative;\n\t\t\t\ttop: -0.05rem;\n\t\t\t}\n\n\t\t\t.message {\n\t\t\t\tborder-left: 1px solid var(--divider);\n\t\t\t\tpadding: 0 0 0 1rem;\n\t\t\t\tmargin: 0 0 0 1rem;\n\t\t\t\tmin-height: 2.5rem;\n\t\t\t\tdisplay: flex;\n\t\t\t\talign-items: center;\n\t\t\t}\n\n\t\t\t.message h1 {\n\t\t\t\tfont-weight: 400;\n\t\t\t\tfont-size: 1em;\n\t\t\t\tmargin: 0;\n\t\t\t}\n\n\t\t\t@media (prefers-color-scheme: dark) {\n\t\t\t\tbody {\n\t\t\t\t\t--bg: #222;\n\t\t\t\t\t--fg: #ddd;\n\t\t\t\t\t--divider: #666;\n\t\t\t\t}\n\t\t\t}\n\t\t</style>\n\t</head>\n\t<body>\n\t\t<div class=\"error\">\n\t\t\t<span class=\"status\">" + status + "</span>\n\t\t\t<div class=\"message\">\n\t\t\t\t<h1>" + message + "</h1>\n\t\t\t</div>\n\t\t</div>\n\t</body>\n</html>\n"
}, },
version_hash: "1pvaiah" version_hash: "n7gbte"
}; };
export async function get_hooks() { export async function get_hooks() {

View File

@@ -16,6 +16,12 @@
> >
Dashboard Dashboard
</a> </a>
<a
href="/migration"
class="text-gray-600 hover:text-blue-600 font-medium {$page.url.pathname.startsWith('/migration') ? 'text-blue-600 border-b-2 border-blue-600' : ''}"
>
Migration
</a>
<a <a
href="/settings" href="/settings"
class="text-gray-600 hover:text-blue-600 font-medium {$page.url.pathname === '/settings' ? 'text-blue-600 border-b-2 border-blue-600' : ''}" class="text-gray-600 hover:text-blue-600 font-medium {$page.url.pathname === '/settings' ? 'text-blue-600 border-b-2 border-blue-600' : ''}"

View File

@@ -21,7 +21,14 @@
environments: [], environments: [],
settings: { settings: {
backup_path: '', backup_path: '',
default_environment_id: null default_environment_id: null,
logging: {
level: 'INFO',
file_path: 'logs/app.log',
max_bytes: 10485760,
backup_count: 5,
enable_belief_state: true
}
} }
}; };
@@ -180,10 +187,43 @@
<label for="backup_path" class="block text-sm font-medium text-gray-700">Backup Storage Path</label> <label for="backup_path" class="block text-sm font-medium text-gray-700">Backup Storage Path</label>
<input type="text" id="backup_path" bind:value={settings.settings.backup_path} class="mt-1 block w-full border border-gray-300 rounded-md shadow-sm p-2" /> <input type="text" id="backup_path" bind:value={settings.settings.backup_path} class="mt-1 block w-full border border-gray-300 rounded-md shadow-sm p-2" />
</div> </div>
<button on:click={handleSaveGlobal} class="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600 w-max">
Save Global Settings
</button>
</div> </div>
<h3 class="text-lg font-medium mb-4 mt-6">Logging Configuration</h3>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label for="log_level" class="block text-sm font-medium text-gray-700">Log Level</label>
<select id="log_level" bind:value={settings.settings.logging.level} class="mt-1 block w-full border border-gray-300 rounded-md shadow-sm p-2">
<option value="DEBUG">DEBUG</option>
<option value="INFO">INFO</option>
<option value="WARNING">WARNING</option>
<option value="ERROR">ERROR</option>
<option value="CRITICAL">CRITICAL</option>
</select>
</div>
<div>
<label for="log_file_path" class="block text-sm font-medium text-gray-700">Log File Path</label>
<input type="text" id="log_file_path" bind:value={settings.settings.logging.file_path} placeholder="logs/app.log" class="mt-1 block w-full border border-gray-300 rounded-md shadow-sm p-2" />
</div>
<div>
<label for="log_max_bytes" class="block text-sm font-medium text-gray-700">Max File Size (MB)</label>
<input type="number" id="log_max_bytes" bind:value={settings.settings.logging.max_bytes} min="1" step="1" class="mt-1 block w-full border border-gray-300 rounded-md shadow-sm p-2" />
</div>
<div>
<label for="log_backup_count" class="block text-sm font-medium text-gray-700">Backup Count</label>
<input type="number" id="log_backup_count" bind:value={settings.settings.logging.backup_count} min="1" step="1" class="mt-1 block w-full border border-gray-300 rounded-md shadow-sm p-2" />
</div>
<div class="md:col-span-2">
<label class="flex items-center">
<input type="checkbox" id="enable_belief_state" bind:checked={settings.settings.logging.enable_belief_state} class="h-4 w-4 text-blue-600 border-gray-300 rounded" />
<span class="ml-2 block text-sm text-gray-900">Enable Belief State Logging</span>
</label>
</div>
</div>
<button on:click={handleSaveGlobal} class="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600 w-max mt-4">
Save Global Settings
</button>
</section> </section>
<section class="mb-8 bg-white p-6 rounded shadow"> <section class="mb-8 bg-white p-6 rounded shadow">

View File

@@ -103,16 +103,25 @@
<p class="mt-1 text-sm text-gray-500">Regular expression to filter dashboards to migrate.</p> <p class="mt-1 text-sm text-gray-500">Regular expression to filter dashboards to migrate.</p>
</div> </div>
<div class="flex items-center mb-8"> <div class="flex items-center justify-between mb-8">
<input <div class="flex items-center">
id="replace-db" <input
type="checkbox" id="replace-db"
bind:checked={replaceDb} type="checkbox"
class="h-4 w-4 text-indigo-600 focus:ring-indigo-500 border-gray-300 rounded" bind:checked={replaceDb}
/> class="h-4 w-4 text-indigo-600 focus:ring-indigo-500 border-gray-300 rounded"
<label for="replace-db" class="ml-2 block text-sm text-gray-900"> />
Replace Database (Apply Mappings) <label for="replace-db" class="ml-2 block text-sm text-gray-900">
</label> Replace Database (Apply Mappings)
</label>
</div>
<a
href="/migration/mappings"
class="text-sm font-medium text-indigo-600 hover:text-indigo-500"
>
Manage Mappings &rarr;
</a>
</div> </div>
<button <button

View File

@@ -0,0 +1,34 @@
# Specification Quality Checklist: Configurable Belief State Logging
**Purpose**: Validate specification completeness and quality before proceeding to planning
**Created**: 2025-12-26
**Feature**: [specs/006-configurable-belief-logs/spec.md](../spec.md)
## Content Quality
- [x] No implementation details (languages, frameworks, APIs)
- [x] Focused on user value and business needs
- [x] Written for non-technical stakeholders
- [x] All mandatory sections completed
## Requirement Completeness
- [x] No [NEEDS CLARIFICATION] markers remain
- [x] Requirements are testable and unambiguous
- [x] Success criteria are measurable
- [x] Success criteria are technology-agnostic (no implementation details)
- [x] All acceptance scenarios are defined
- [x] Edge cases are identified
- [x] Scope is clearly bounded
- [x] Dependencies and assumptions identified
## Feature Readiness
- [x] All functional requirements have clear acceptance criteria
- [x] User scenarios cover primary flows
- [x] Feature meets measurable outcomes defined in Success Criteria
- [x] No implementation details leak into specification
## Notes
- Items marked incomplete require spec updates before `/speckit.clarify` or `/speckit.plan`

View File

@@ -0,0 +1,56 @@
# API Contract: Settings Update
## PATCH /api/settings/global
Updates the global application settings, including the new logging configuration.
### Request Body
**Content-Type**: `application/json`
```json
{
"backup_path": "string",
"default_environment_id": "string (optional)",
"logging": {
"level": "string (DEBUG, INFO, WARNING, ERROR, CRITICAL)",
"file_path": "string (optional)",
"max_bytes": "integer (default: 10485760)",
"backup_count": "integer (default: 5)",
"enable_belief_state": "boolean (default: true)"
}
}
```
### Response
**Status**: `200 OK`
**Content-Type**: `application/json`
```json
{
"backup_path": "string",
"default_environment_id": "string (optional)",
"logging": {
"level": "string",
"file_path": "string",
"max_bytes": "integer",
"backup_count": "integer",
"enable_belief_state": "boolean"
}
}
```
### Example
**Request**
```json
{
"backup_path": "backups",
"logging": {
"level": "DEBUG",
"file_path": "logs/app.log",
"enable_belief_state": true
}
}

View File

@@ -0,0 +1,74 @@
# Data Model: Configurable Belief State Logging
## 1. Configuration Models
These models extend the existing `ConfigModels` in `backend/src/core/config_models.py`.
### 1.1. LoggingConfig
Defines the configuration for the application's logging system.
| Field | Type | Default | Description |
|---|---|---|---|
| `level` | `str` | `"INFO"` | The logging level (DEBUG, INFO, WARNING, ERROR, CRITICAL). |
| `file_path` | `Optional[str]` | `"logs/app.log"` | Path to the log file. If None, file logging is disabled. |
| `max_bytes` | `int` | `10485760` (10MB) | Maximum size of a log file before rotation. |
| `backup_count` | `int` | `5` | Number of backup files to keep. |
| `enable_belief_state` | `bool` | `True` | Whether to enable structured Belief State logging (Entry/Exit). |
```python
class LoggingConfig(BaseModel):
level: str = "INFO"
file_path: Optional[str] = "logs/app.log"
max_bytes: int = 10 * 1024 * 1024
backup_count: int = 5
enable_belief_state: bool = True
```
### 1.2. GlobalSettings (Updated)
Updates the existing `GlobalSettings` to include `LoggingConfig`.
| Field | Type | Default | Description |
|---|---|---|---|
| `logging` | `LoggingConfig` | `LoggingConfig()` | The logging configuration object. |
```python
class GlobalSettings(BaseModel):
backup_path: str
default_environment_id: Optional[str] = None
logging: LoggingConfig = Field(default_factory=LoggingConfig)
```
## 2. Logger Entities
These entities are part of the `backend/src/core/logger.py` module.
### 2.1. LogEntry (Existing)
Represents a single log record.
| Field | Type | Description |
|---|---|---|
| `timestamp` | `datetime` | UTC timestamp of the log. |
| `level` | `str` | Log level. |
| `message` | `str` | Log message. |
| `context` | `Optional[Dict[str, Any]]` | Additional context data. |
### 2.2. BeliefState (Concept)
Represents the state of execution in the "Belief State" model.
- **Entry**: Entering a logical block.
- **Action**: Performing a core action within the block.
- **Coherence**: Verifying the state (OK or Failed).
- **Exit**: Exiting the logical block.
Format: `[{ANCHOR_ID}][{STATE}] {Message}`
## 3. Relationships
- `AppConfig` contains `GlobalSettings`.
- `GlobalSettings` contains `LoggingConfig`.
- `ConfigManager` reads/writes `AppConfig`.
- `ConfigManager` configures the global `logger` based on `LoggingConfig`.

View File

@@ -0,0 +1,81 @@
# Implementation Plan: Configurable Belief State Logging
**Branch**: `006-configurable-belief-logs` | **Date**: 2025-12-26 | **Spec**: specs/006-configurable-belief-logs/spec.md
**Input**: Feature specification from `/specs/006-configurable-belief-logs/spec.md`
**Note**: This template is filled in by the `/speckit.plan` command. See `.specify/templates/commands/plan.md` for the execution workflow.
## Summary
Implement a configurable logging system with "Belief State" support (Entry, Action, Coherence, Exit).
Approach: Use Python's `logging` module with a custom Context Manager (`belief_scope`) and extend `GlobalSettings` with a `LoggingConfig` model.
## Technical Context
**Language/Version**: Python 3.9+
**Primary Dependencies**: FastAPI (Backend), Pydantic (Config), Svelte (Frontend)
**Storage**: Filesystem (for log files), JSON (for configuration persistence)
**Testing**: pytest (Backend), manual verification (Frontend)
**Target Platform**: Linux server (primary), cross-platform compatible
**Project Type**: Web application (Backend + Frontend)
**Performance Goals**: Low overhead logging (<1ms per log entry), non-blocking for main thread (mostly)
**Constraints**: Must use standard library `logging` where possible; Log rotation to prevent disk overflow
**Scale/Scope**: Configurable log levels and retention policies
## Constitution Check
*GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.*
- **Library-First**: N/A (Core infrastructure)
- **CLI Interface**: N/A (Configured via UI/API/JSON)
- **Test-First**: Will require unit tests for `belief_scope` and config updates.
- **Integration Testing**: Will require testing the settings API.
- **Observability**: This feature *is* the observability enhancement.
## Project Structure
### Documentation (this feature)
```text
specs/[###-feature]/
├── plan.md # This file (/speckit.plan command output)
├── research.md # Phase 0 output (/speckit.plan command)
├── data-model.md # Phase 1 output (/speckit.plan command)
├── quickstart.md # Phase 1 output (/speckit.plan command)
├── contracts/ # Phase 1 output (/speckit.plan command)
└── tasks.md # Phase 2 output (/speckit.tasks command - NOT created by /speckit.plan)
```
### Source Code (repository root)
```text
backend/
├── src/
│ ├── core/
│ │ ├── config_models.py # Add LoggingConfig
│ │ ├── config_manager.py # Update config loading/saving
│ │ └── logger.py # Add belief_scope and configure_logger
│ └── api/
│ └── routes/
│ └── settings.py # Update GlobalSettings endpoint
└── tests/
└── test_logger.py # New tests for logger logic
frontend/
├── src/
│ ├── pages/
│ │ └── Settings.svelte # Add logging UI controls
│ └── lib/
│ └── api.js # Update API calls if needed
```
**Structure Decision**: enhancing existing Backend/Frontend structure.
## Complexity Tracking
> **Fill ONLY if Constitution Check has violations that must be justified**
| Violation | Why Needed | Simpler Alternative Rejected Because |
|-----------|------------|-------------------------------------|
| [e.g., 4th project] | [current need] | [why 3 projects insufficient] |
| [e.g., Repository pattern] | [specific problem] | [why direct DB access insufficient] |

View File

@@ -0,0 +1,104 @@
# Quickstart: Configurable Belief State Logging
## 1. Configuration
The logging system is configured via the `GlobalSettings` in `config.json` or through the Settings UI.
### 1.1. Default Configuration
```json
{
"environments": [],
"settings": {
"backup_path": "backups",
"logging": {
"level": "INFO",
"file_path": "logs/app.log",
"max_bytes": 10485760,
"backup_count": 5,
"enable_belief_state": true
}
}
}
```
### 1.2. Changing Log Level
To change the log level to `DEBUG`, update the `logging.level` field in `config.json` or use the API:
```bash
curl -X PATCH http://localhost:8000/api/settings/global \
-H "Content-Type: application/json" \
-d '{
"backup_path": "backups",
"logging": {
"level": "DEBUG",
"file_path": "logs/app.log",
"max_bytes": 10485760,
"backup_count": 5,
"enable_belief_state": true
}
}'
```
## 2. Using Belief State Logging
### 2.1. Basic Usage
Use the `belief_scope` context manager to automatically log Entry, Exit, and Coherence states.
```python
from backend.src.core.logger import logger, belief_scope
def my_function():
with belief_scope("MyFunction"):
# Logs: [MyFunction][Entry]
logger.info("Doing something important")
# Logs: [MyFunction][Action] Doing something important
# ... logic ...
# Logs: [MyFunction][Coherence:OK]
# Logs: [MyFunction][Exit]
```
### 2.2. Error Handling
If an exception occurs within the scope, it is caught, logged as a failure, and re-raised.
```python
def failing_function():
with belief_scope("FailingFunc"):
raise ValueError("Something went wrong")
# Logs: [FailingFunc][Entry]
# Logs: [FailingFunc][Coherence:Failed] Something went wrong
# Re-raises ValueError
```
### 2.3. Custom Messages
You can provide an optional message to `belief_scope`.
```python
with belief_scope("DataProcessor", "Processing batch #1"):
# Logs: [DataProcessor][Entry] Processing batch #1
pass
```
## 3. Log Output Format
### 3.1. Standard Log
```text
[2025-12-26 10:00:00,000][INFO][superset_tools_app] System initialized
```
### 3.2. Belief State Log
```text
[2025-12-26 10:00:01,000][INFO][superset_tools_app] [MyFunction][Entry]
[2025-12-26 10:00:01,050][INFO][superset_tools_app] [MyFunction][Action] Processing data
[2025-12-26 10:00:01,100][INFO][superset_tools_app] [MyFunction][Coherence:OK]
[2025-12-26 10:00:01,100][INFO][superset_tools_app] [MyFunction][Exit]

View File

@@ -0,0 +1,109 @@
# Research: Configurable Belief State Logging
## 1. Introduction
This research explores the implementation of a configurable logging system that supports "Belief State" logging (Entry, Action, Coherence, Exit) and allows users to customize log levels, formats, and file persistence.
## 2. Analysis of Existing System
- **Language**: Python 3.9+ (Backend)
- **Framework**: FastAPI (inferred from context, though not explicitly seen in snippets, `uvicorn` mentioned)
- **Logging**: Standard Python `logging` module.
- **Configuration**: Pydantic models (`ConfigModels`) managed by `ConfigManager`, persisting to `config.json`.
- **Current Logger**:
- `backend/src/core/logger.py`:
- Uses `logging.getLogger("superset_tools_app")`
- Has `StreamHandler` (console) and `WebSocketLogHandler` (streaming).
- `WebSocketLogHandler` buffers logs in a `deque`.
## 3. Requirements Analysis
- **Belief State**: Need a structured way to log `[ANCHOR_ID][STATE] Message`.
- **Context Manager**: Need a `with belief_scope("ID"):` pattern.
- **Configuration**: Need to add `LoggingConfig` to `GlobalSettings`.
- **File Logging**: Need `RotatingFileHandler` with size limits.
- **Dynamic Reconfiguration**: Need to update logger handlers/levels when config changes.
## 4. Proposed Solution
### 4.1. Data Model (`LoggingConfig`)
We will add a `LoggingConfig` model to `backend/src/core/config_models.py`:
```python
class LoggingConfig(BaseModel):
level: str = "INFO" # DEBUG, INFO, WARNING, ERROR, CRITICAL
file_path: Optional[str] = "logs/app.log"
max_bytes: int = 10 * 1024 * 1024 # 10MB
backup_count: int = 5
enable_belief_state: bool = True
```
And update `GlobalSettings`:
```python
class GlobalSettings(BaseModel):
backup_path: str
default_environment_id: Optional[str] = None
logging: LoggingConfig = Field(default_factory=LoggingConfig)
```
### 4.2. Context Manager (`belief_scope`)
We will implement a context manager in `backend/src/core/logger.py`:
```python
from contextlib import contextmanager
@contextmanager
def belief_scope(anchor_id: str, message: str = ""):
# ... logic to log [Entry] ...
try:
yield
# ... logic to log [Coherence:OK] and [Exit] ...
except Exception as e:
# ... logic to log [Coherence:Failed] ...
raise
```
### 4.3. Logger Reconfiguration
We will add a `configure_logger(config: LoggingConfig)` function in `backend/src/core/logger.py` that:
1. Sets the logger level.
2. Removes old file handlers.
3. Adds a new `RotatingFileHandler` if `file_path` is set.
4. Updates a global flag for `enable_belief_state`.
`ConfigManager` will call this function whenever settings are updated.
### 4.4. Belief State Filtering
If `enable_belief_state` is False:
- `Entry`/`Exit` logs are skipped.
- `Action`/`Coherence` logs are logged as standard messages (maybe without the `[ANCHOR_ID]` prefix if desired, but retaining it is usually better for consistency).
## 5. Alternatives Considered
- **Alternative A**: Use a separate logger for Belief State.
- *Pros*: Cleaner separation.
- *Cons*: Harder to interleave with standard logs in the same stream/file.
- *Decision*: Rejected. We want a unified log stream.
- **Alternative B**: Use structlog.
- *Pros*: Powerful structured logging.
- *Cons*: Adds a new dependency.
- *Decision*: Rejected. Standard `logging` is sufficient and already used.
## 6. Implementation Steps
1. **Modify `backend/src/core/config_models.py`**: Add `LoggingConfig` and update `GlobalSettings`.
2. **Modify `backend/src/core/logger.py`**:
- Add `configure_logger` function.
- Implement `belief_scope` context manager.
- Implement `RotatingFileHandler`.
3. **Modify `backend/src/core/config_manager.py`**: Call `configure_logger` on init and update.
4. **Frontend**: Update Settings page to allow editing `LoggingConfig`.
## 7. Conclusion
The proposed solution leverages the existing Pydantic/ConfigManager architecture and standard Python logging, minimizing disruption while meeting all requirements.

View File

@@ -0,0 +1,88 @@
# Feature Specification: Configurable Belief State Logging
**Feature Branch**: `006-configurable-belief-logs`
**Created**: 2025-12-26
**Status**: Draft
**Input**: User description: "Меня не устраивает текущее состояния логов проекта, которые я получаю после запуска run.sh. Нужно продумать систему хранения логов, которая будет настраиваемой, включать логирование belief state агента разработки (смотри semantic_protocol.md) и гибко настраиваемой."
## Clarifications
### Session 2025-12-26
- Q: Log rotation policy? → A: Size-based rotation (e.g., 10MB limit, 5 backups).
- Q: Belief State disabled behavior? → A: Smart Filter (Suppress Entry/Exit; keep Action/Coherence as standard logs).
- Q: Implementation pattern? → A: Context Manager (e.g., `with belief_scope("ID"):`) to auto-handle Entry/Exit/Error.
## User Scenarios & Testing
### User Story 1 - Structured Belief State Logging (Priority: P1)
As a developer or AI agent, I want logs to clearly indicate the execution flow and internal state ("Belief State") of the system using a standardized format, so that I can easily debug logic errors and verify semantic coherence.
**Why this priority**: This is the core requirement to align with the `semantic_protocol.md` and improve debugging capabilities for the AI agent.
**Independent Test**: Trigger an action in the system (e.g., running a migration) and verify that the logs contain entries formatted as `[ANCHOR_ID][STATE] Message`, representing the entry, action, and exit states of the code blocks.
**Acceptance Scenarios**:
1. **Given** a function wrapped with a Belief State logger, **When** the function is called, **Then** a log entry `[ANCHOR_ID][Entry] ...` is generated.
2. **Given** a function execution, **When** the core logic is executed, **Then** a log entry `[ANCHOR_ID][Action] ...` is generated.
3. **Given** a function execution completes successfully, **When** it returns, **Then** a log entry `[ANCHOR_ID][Coherence:OK]` and `[ANCHOR_ID][Exit]` are generated.
4. **Given** a function execution fails, **When** an exception is raised, **Then** a log entry `[ANCHOR_ID][Coherence:Failed]` is generated.
---
### User Story 2 - Configurable Logging Settings (Priority: P2)
As a user, I want to be able to configure log levels, formats, and destinations via the application settings, so that I can control the verbosity and storage of logs without changing code.
**Why this priority**: Provides the "flexible" and "customizable" aspect of the requirement, allowing users to adapt logging to their needs (dev vs prod).
**Independent Test**: Change the log level in the settings (e.g., from INFO to DEBUG) and verify that debug messages start appearing in the output.
**Acceptance Scenarios**:
1. **Given** the application is running, **When** I update the log level to "DEBUG" in the settings, **Then** debug-level logs are captured and displayed.
2. **Given** the application is running, **When** I enable "File Logging" in settings, **Then** logs are written to the specified file path.
3. **Given** the application is running, **When** I disable "Belief State" logs in settings, **Then** "Entry" and "Exit" logs are suppressed, while "Action" and "Coherence" logs are retained as standard log entries.
---
### Edge Cases
- **Invalid Configuration**: What happens if the user provides an invalid log level (e.g., "SUPER_LOUD")? The system should fallback to a safe default (e.g., "INFO") and log a warning.
- **File System Errors**: What happens if the configured log file path is not writable? The system should fallback to console logging and alert the user.
- **High Volume**: What happens if "Belief State" logging generates too much noise? The system should remain performant, and users should be able to toggle it off easily.
## Requirements
### Functional Requirements
- **FR-001**: The system MUST support a `LoggingConfig` structure within the global configuration to store settings for level, format, file path, and belief state enablement.
- **FR-002**: The logging system MUST implement a standard format for "Belief State" logs as defined in the system protocols: `[{ANCHOR_ID}][{STATE}] {Message}`.
- **FR-003**: The system MUST provide a standard mechanism to easily generate Belief State logs without repetitive boilerplate code, specifically using a **Context Manager** pattern.
- **FR-004**: The supported Belief States MUST include: `Entry`, `Action`, `Coherence:OK`, `Coherence:Failed`, `Exit`.
- **FR-005**: The system MUST allow dynamic reconfiguration of the logger (e.g., changing levels) when settings are updated.
- **FR-006**: The real-time log stream MUST preserve the structured format of Belief State logs so the frontend can potentially render them specially.
- **FR-007**: Logs MUST optionally be persisted to a file if configured.
- **FR-008**: The file logging system MUST implement size-based rotation (default: 10MB limit, 5 backups) to prevent disk saturation.
- **FR-009**: When `enable_belief_state` is False, the logger MUST suppress `Entry` and `Exit` states but retain `Action` and `Coherence` states (stripping the structured prefix if necessary or keeping it as standard info).
- **FR-010**: The Context Manager MUST automatically log `[Entry]` on start, `[Exit]` on success, and `[Coherence:Failed]` if an exception occurs (re-raising the exception).
### Key Entities
- **LoggingConfig**: A configuration object defining `level` (text), `file_path` (text), `format` (structure), `enable_belief_state` (boolean).
- **BeliefStateAdapter**: A component that enforces the semantic protocol format for log entries.
## Success Criteria
### Measurable Outcomes
- **SC-001**: Developers can trace the execution flow of a specific task using only the `[ANCHOR_ID]` filtered logs.
- **SC-002**: Changing the log level in the configuration file or API immediately (or upon restart) reflects in the log output.
- **SC-003**: All "Belief State" logs strictly follow the `[ID][State]` format, allowing for regex parsing.
- **SC-004**: System supports logging to both console and file simultaneously if configured.
## Assumptions
- The existing real-time streaming mechanism can be adapted or chained with the new logging configuration.
- The system protocol definition of states is the source of truth.

View File

@@ -0,0 +1,61 @@
# Tasks: Configurable Belief State Logging
**Spec**: `specs/006-configurable-belief-logs/spec.md`
**Plan**: `specs/006-configurable-belief-logs/plan.md`
**Status**: Completed
## Phase 1: Setup
*Goal: Initialize project structure for logging.*
- [x] T001 Ensure logs directory exists at `logs/` (relative to project root)
## Phase 2: Foundational
*Goal: Define data models and infrastructure required for logging configuration.*
- [x] T002 Define `LoggingConfig` model in `backend/src/core/config_models.py`
- [x] T003 Update `GlobalSettings` model to include `logging` field in `backend/src/core/config_models.py`
- [x] T004 Update `ConfigManager` to handle logging configuration persistence in `backend/src/core/config_manager.py`
## Phase 3: User Story 1 - Structured Belief State Logging
*Goal: Implement the core "Belief State" logging logic with context managers.*
*Priority: P1*
**Independent Test**: Run `pytest backend/tests/test_logger.py` and verify `belief_scope` generates `[ID][Entry]`, `[ID][Action]`, and `[ID][Exit]` logs.
- [x] T005 [US1] Create unit tests for belief state logging in `backend/tests/test_logger.py`
- [x] T006 [US1] Implement `belief_scope` context manager in `backend/src/core/logger.py`
- [x] T007 [US1] Implement log formatting and smart filtering (suppress Entry/Exit if disabled) in `backend/src/core/logger.py`
## Phase 4: User Story 2 - Configurable Logging Settings
*Goal: Expose logging configuration to the user via API and UI.*
*Priority: P2*
**Independent Test**: Update settings via API/UI and verify log level changes (e.g., DEBUG logs appear) and file rotation is active.
- [x] T008 [US2] Implement `configure_logger` function to apply settings (level, file, rotation) in `backend/src/core/logger.py`
- [x] T009 [US2] Update settings API endpoint to handle `logging` updates in `backend/src/api/routes/settings.py`
- [x] T010 [P] [US2] Add Logging configuration section (Level, File Path, Enable Belief State) to `frontend/src/pages/Settings.svelte`
## Final Phase: Polish & Cross-Cutting Concerns
*Goal: Verify system stability and cleanup.*
- [x] T011 Verify log rotation works by generating dummy logs (manual verification)
- [x] T012 Ensure default configuration provides a sensible out-of-the-box experience
## Dependencies
```mermaid
graph TD
Setup[Phase 1: Setup] --> Foundational[Phase 2: Foundational]
Foundational --> US1[Phase 3: US1 Belief State]
US1 --> US2[Phase 4: US2 Configurable Settings]
US2 --> Polish[Final Phase: Polish]
```
## Parallel Execution Opportunities
- **US2**: Frontend task (T010) can be implemented in parallel with Backend tasks (T008, T009) once the API contract is finalized.
## Implementation Strategy
1. **MVP**: Complete Phase 1, 2, and 3 to enable structured logging for the agent.
2. **Full Feature**: Complete Phase 4 to allow users to control the verbosity and storage.

View File

@@ -28,7 +28,11 @@ class SupersetLogger:
# @PARAM: log_dir (Optional[Path]) - Директория для сохранения лог-файлов. # @PARAM: log_dir (Optional[Path]) - Директория для сохранения лог-файлов.
# @PARAM: level (int) - Уровень логирования (e.g., `logging.INFO`). # @PARAM: level (int) - Уровень логирования (e.g., `logging.INFO`).
# @PARAM: console (bool) - Флаг для включения вывода в консоль. # @PARAM: console (bool) - Флаг для включения вывода в консоль.
def __init__(self, name: str = "superset_tool", log_dir: Optional[Path] = None, level: int = logging.INFO, console: bool = True) -> None: def __init__(self, name: str = "superset_tool", log_dir: Optional[Path] = None, level: int = logging.INFO, console: bool = True, logger: Optional[logging.Logger] = None) -> None:
if logger:
self.logger = logger
return
self.logger = logging.getLogger(name) self.logger = logging.getLogger(name)
self.logger.setLevel(level) self.logger.setLevel(level)
self.logger.propagate = False self.logger.propagate = False