Skip to content

feat: runtime sink configuration via SettingsService#934

Merged
Aureliolo merged 5 commits intomainfrom
feat/runtime-sink-config
Mar 30, 2026
Merged

feat: runtime sink configuration via SettingsService#934
Aureliolo merged 5 commits intomainfrom
feat/runtime-sink-config

Conversation

@Aureliolo
Copy link
Copy Markdown
Owner

Summary

  • Add per-sink enable/disable, level/format overrides, rotation config, and custom sink creation -- all hot-reloadable without restart via ObservabilitySettingsSubscriber
  • Two new JSON settings (sink_overrides, custom_sinks) in the observability namespace with full validation (console sink protection, path traversal prevention, duplicate path detection)
  • Extend configure_logging and build_handler to support custom routing overrides for user-defined sinks
  • Fix MSW postMessage handler missing origin check (CodeQL alert feat: implement crash recovery with fail-and-reassign strategy #149, CWE-020/CWE-940)

Test plan

  • 44 unit tests for sink_config_builder (overrides, custom sinks, validation, combined, edge cases)
  • 16 unit tests for ObservabilitySettingsSubscriber (protocol, rebuild, error handling, namespace guard, idempotency)
  • 6 integration tests for hot reload (disable sink, level change, custom sink, routing filters, module-level loggers, message preservation)
  • All 616 observability + settings tests pass
  • ruff lint + format clean
  • mypy strict pass
  • Pre-reviewed by 4 agents, 13 findings addressed

Closes #564

🤖 Generated with Claude Code

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 30, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 59770d73-0aa7-4224-adae-49b45e970600

📥 Commits

Reviewing files that changed from the base of the PR and between 97042dc and e319311.

📒 Files selected for processing (3)
  • docs/design/operations.md
  • src/synthorg/observability/sink_config_builder.py
  • src/synthorg/settings/subscribers/observability_subscriber.py
📜 Recent review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
  • GitHub Check: Test (Python 3.14)
  • GitHub Check: Dashboard Test
  • GitHub Check: Build Sandbox
  • GitHub Check: Build Backend
  • GitHub Check: Build Web
  • GitHub Check: Dependency Review
  • GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{py,ts,tsx,md}

📄 CodeRabbit inference engine (CLAUDE.md)

Vendor-agnostic everywhere: NEVER use real vendor names (Anthropic, OpenAI, Claude, GPT, etc.) in project-owned code, docstrings, comments, tests, or config examples; use generic names: example-provider, example-large-001, etc.; vendor names only in: design spec, .claude/ files, third-party imports, provider presets

Files:

  • docs/design/operations.md
  • src/synthorg/settings/subscribers/observability_subscriber.py
  • src/synthorg/observability/sink_config_builder.py
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.py: No from __future__ import annotations—Python 3.14 has PEP 649
PEP 758 except syntax: use except A, B: (no parentheses)—ruff enforces this on Python 3.14
Type hints: all public functions, mypy strict mode
Docstrings: Google style, required on public classes/functions (enforced by ruff D rules)
Immutability: create new objects, never mutate existing ones. For non-Pydantic internal collections (registries, BaseTool), use copy.deepcopy() at construction + MappingProxyType wrapping for read-only enforcement
Config vs runtime state: frozen Pydantic models for config/identity; separate mutable-via-copy models (using model_copy(update=...)) for runtime state that evolves. Never mix static config fields with mutable runtime fields in one model
Models: Pydantic v2 (BaseModel, model_validator, computed_field, ConfigDict). Adopted conventions: use allow_inf_nan=False in all ConfigDict declarations; use @computed_field for derived values; use NotBlankStr for all identifier/name fields
Async concurrency: prefer asyncio.TaskGroup for fan-out/fan-in parallel operations in new code; prefer structured concurrency over bare create_task
Line length: 88 characters (ruff)
Functions: < 50 lines, files < 800 lines
Errors: handle explicitly, never silently swallow
Validate: at system boundaries (user input, external APIs, config files)

Files:

  • src/synthorg/settings/subscribers/observability_subscriber.py
  • src/synthorg/observability/sink_config_builder.py
src/synthorg/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

src/synthorg/**/*.py: Every module with business logic MUST have: from synthorg.observability import get_logger then logger = get_logger(name)
Variable name: always logger (not _logger, not log)
Event names: always use constants from domain-specific modules under synthorg.observability.events; import directly from synthorg.observability.events.
Structured kwargs: always logger.info(EVENT, key=value)—never logger.info('msg %s', val)
All error paths must log at WARNING or ERROR with context before raising
All state transitions must log at INFO
DEBUG for object creation, internal flow, entry/exit of key functions
All provider calls go through BaseCompletionProvider which applies retry + rate limiting automatically; never implement retry logic in driver subclasses or calling code

Files:

  • src/synthorg/settings/subscribers/observability_subscriber.py
  • src/synthorg/observability/sink_config_builder.py
src/synthorg/**/!(setup|sinks).py

📄 CodeRabbit inference engine (CLAUDE.md)

Never use import logging / logging.getLogger() / print() in application code; exception: observability/setup.py and observability/sinks.py may use stdlib logging and print() for bootstrap code

Files:

  • src/synthorg/settings/subscribers/observability_subscriber.py
  • src/synthorg/observability/sink_config_builder.py
🧠 Learnings (35)
📓 Common learnings
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/settings/**/*.py : Settings package (settings/): runtime-editable settings persistence (DB > env > YAML > code defaults), typed definitions (9 namespaces), Fernet encryption for sensitive values, config bridge (JSON serialization for Pydantic/collections), ConfigResolver (typed accessors), validation, registry, change notifications via message bus, SettingsSubscriber protocol, SettingsChangeDispatcher (polls `#settings` channel, routes to subscribers, restart_required filtering)
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T06:30:14.180Z
Learning: Applies to src/synthorg/observability/**/*.py : Observability includes structured logging via `get_logger(__name__)`, correlation tracking, and log sinks.
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/observability/**/*.py : Observability package (observability/): structured logging, correlation tracking, log sinks; event constants organized by domain under observability/events/ (e.g., events.api, events.tool, events.git, events.context_budget, events.backup)
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T06:24:56.341Z
Learning: Applies to src/synthorg/observability/**/*.py : Observability must use structured logging with correlation tracking and log sinks
📚 Learning: 2026-03-16T06:24:56.341Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T06:24:56.341Z
Learning: Applies to src/synthorg/observability/**/*.py : Observability must use structured logging with correlation tracking and log sinks

Applied to files:

  • docs/design/operations.md
  • src/synthorg/settings/subscribers/observability_subscriber.py
  • src/synthorg/observability/sink_config_builder.py
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/settings/**/*.py : Settings package (settings/): runtime-editable settings persistence (DB > env > YAML > code defaults), typed definitions (9 namespaces), Fernet encryption for sensitive values, config bridge (JSON serialization for Pydantic/collections), ConfigResolver (typed accessors), validation, registry, change notifications via message bus, SettingsSubscriber protocol, SettingsChangeDispatcher (polls `#settings` channel, routes to subscribers, restart_required filtering)

Applied to files:

  • docs/design/operations.md
  • src/synthorg/settings/subscribers/observability_subscriber.py
  • src/synthorg/observability/sink_config_builder.py
📚 Learning: 2026-03-17T06:30:14.180Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T06:30:14.180Z
Learning: Applies to src/synthorg/observability/**/*.py : Observability includes structured logging via `get_logger(__name__)`, correlation tracking, and log sinks.

Applied to files:

  • docs/design/operations.md
  • src/synthorg/settings/subscribers/observability_subscriber.py
  • src/synthorg/observability/sink_config_builder.py
📚 Learning: 2026-03-17T06:30:14.180Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T06:30:14.180Z
Learning: Applies to src/synthorg/settings/**/*.py : Settings use runtime-editable persistence with precedence: DB > env > YAML > code defaults. 8 namespaces with Fernet encryption for sensitive values.

Applied to files:

  • docs/design/operations.md
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Settings: Runtime-editable settings persistence (DB > env > YAML > code defaults), typed definitions (9 namespaces), Fernet encryption for sensitive values, config bridge, ConfigResolver (typed composed reads for controllers), validation, registry, change notifications via message bus. Per-namespace setting definitions in definitions/ submodule (api, company, providers, memory, budget, security, coordination, observability, backup).

Applied to files:

  • docs/design/operations.md
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/observability/**/*.py : Observability package (observability/): structured logging, correlation tracking, log sinks; event constants organized by domain under observability/events/ (e.g., events.api, events.tool, events.git, events.context_budget, events.backup)

Applied to files:

  • docs/design/operations.md
  • src/synthorg/settings/subscribers/observability_subscriber.py
  • src/synthorg/observability/sink_config_builder.py
📚 Learning: 2026-03-20T21:44:04.528Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T21:44:04.528Z
Learning: Applies to **/*.py : Use `except A, B:` syntax (without parentheses) per PEP 758 for exception handling in Python 3.14

Applied to files:

  • src/synthorg/settings/subscribers/observability_subscriber.py
📚 Learning: 2026-03-16T07:22:28.134Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T07:22:28.134Z
Learning: Applies to **/*.py : Handle errors explicitly; never silently swallow exceptions

Applied to files:

  • src/synthorg/settings/subscribers/observability_subscriber.py
📚 Learning: 2026-03-14T15:43:05.601Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T15:43:05.601Z
Learning: Applies to **/*.py : Handle errors explicitly, never silently swallow exceptions

Applied to files:

  • src/synthorg/settings/subscribers/observability_subscriber.py
📚 Learning: 2026-03-16T07:22:28.134Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T07:22:28.134Z
Learning: Applies to **/*.py : Use `except A, B:` syntax (no parentheses) for exception handling — PEP 758 exception syntax enforced by ruff on Python 3.14

Applied to files:

  • src/synthorg/settings/subscribers/observability_subscriber.py
📚 Learning: 2026-03-14T16:18:57.267Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T16:18:57.267Z
Learning: Applies to **/*.py : Handle errors explicitly—never silently swallow exceptions.

Applied to files:

  • src/synthorg/settings/subscribers/observability_subscriber.py
📚 Learning: 2026-03-14T16:18:57.267Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T16:18:57.267Z
Learning: Applies to **/*.py : Use PEP 758 except syntax with `except A, B:` (no parentheses) for multiple exceptions—ruff enforces this on Python 3.14.

Applied to files:

  • src/synthorg/settings/subscribers/observability_subscriber.py
📚 Learning: 2026-03-14T15:43:05.601Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T15:43:05.601Z
Learning: Applies to **/*.py : Use PEP 758 except syntax: `except A, B:` (no parentheses) — enforced by ruff on Python 3.14

Applied to files:

  • src/synthorg/settings/subscribers/observability_subscriber.py
📚 Learning: 2026-03-30T16:36:12.059Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-30T16:36:12.059Z
Learning: Applies to **/*.py : PEP 758 except syntax: use `except A, B:` (no parentheses)—ruff enforces this on Python 3.14

Applied to files:

  • src/synthorg/settings/subscribers/observability_subscriber.py
📚 Learning: 2026-03-15T16:55:07.730Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T16:55:07.730Z
Learning: Applies to **/*.py : Use PEP 758 except syntax: use `except A, B:` (no parentheses) — ruff enforces this on Python 3.14.

Applied to files:

  • src/synthorg/settings/subscribers/observability_subscriber.py
📚 Learning: 2026-03-30T16:36:12.059Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-30T16:36:12.059Z
Learning: Applies to **/*.py : Errors: handle explicitly, never silently swallow

Applied to files:

  • src/synthorg/settings/subscribers/observability_subscriber.py
📚 Learning: 2026-03-30T16:36:12.059Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-30T16:36:12.059Z
Learning: Applies to src/synthorg/**/!(setup|sinks).py : Never use import logging / logging.getLogger() / print() in application code; exception: observability/setup.py and observability/sinks.py may use stdlib logging and print() for bootstrap code

Applied to files:

  • src/synthorg/settings/subscribers/observability_subscriber.py
📚 Learning: 2026-03-14T16:18:57.267Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T16:18:57.267Z
Learning: Applies to src/ai_company/!(observability)/**/*.py : All error paths must log at WARNING or ERROR with context before raising.

Applied to files:

  • src/synthorg/settings/subscribers/observability_subscriber.py
📚 Learning: 2026-03-17T06:43:14.114Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T06:43:14.114Z
Learning: Applies to src/synthorg/**/*.py : All error paths must log at WARNING or ERROR with context before raising. All state transitions must log at INFO. DEBUG for object creation, internal flow, entry/exit of key functions. Pure data models, enums, and re-exports do NOT need logging.

Applied to files:

  • src/synthorg/settings/subscribers/observability_subscriber.py
  • src/synthorg/observability/sink_config_builder.py
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Applies to src/synthorg/**/*.py : All error paths must log at WARNING or ERROR with context before raising. All state transitions must log at INFO. DEBUG for object creation, internal flow, entry/exit of key functions.

Applied to files:

  • src/synthorg/settings/subscribers/observability_subscriber.py
  • src/synthorg/observability/sink_config_builder.py
📚 Learning: 2026-03-16T07:22:28.134Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T07:22:28.134Z
Learning: Applies to src/synthorg/**/*.py : All error paths must log at WARNING or ERROR with context before raising. All state transitions must log at INFO. DEBUG for object creation, internal flow, and key function entry/exit

Applied to files:

  • src/synthorg/settings/subscribers/observability_subscriber.py
📚 Learning: 2026-03-19T11:33:01.580Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T11:33:01.580Z
Learning: Applies to src/synthorg/**/*.py : Never use `import logging`, `logging.getLogger()`, or `print()` in application code; use the injected logger instead

Applied to files:

  • src/synthorg/settings/subscribers/observability_subscriber.py
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/**/*.py : Never use `import logging` / `logging.getLogger()` / `print()` in application code

Applied to files:

  • src/synthorg/settings/subscribers/observability_subscriber.py
📚 Learning: 2026-03-15T18:38:44.202Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:38:44.202Z
Learning: Applies to src/synthorg/**/*.py : Never use `import logging`, `logging.getLogger()`, or `print()` in application code — use the synthorg logger instead

Applied to files:

  • src/synthorg/settings/subscribers/observability_subscriber.py
📚 Learning: 2026-03-15T18:28:13.207Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:28:13.207Z
Learning: Applies to src/synthorg/**/*.py : Every module with business logic MUST have: `from synthorg.observability import get_logger` then `logger = get_logger(__name__)`. Never use `import logging` / `logging.getLogger()` / `print()` in application code. Variable name: always `logger` (not `_logger`, not `log`).

Applied to files:

  • src/synthorg/settings/subscribers/observability_subscriber.py
  • src/synthorg/observability/sink_config_builder.py
📚 Learning: 2026-03-17T06:43:14.114Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T06:43:14.114Z
Learning: Applies to src/synthorg/**/*.py : Every module with business logic MUST have: `from synthorg.observability import get_logger` then `logger = get_logger(__name__)`. Never use `import logging` / `logging.getLogger()` / `print()` in application code. Variable name: always `logger`.

Applied to files:

  • src/synthorg/settings/subscribers/observability_subscriber.py
  • src/synthorg/observability/sink_config_builder.py
📚 Learning: 2026-03-15T19:14:27.144Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T19:14:27.144Z
Learning: Applies to src/synthorg/**/*.py : Every module with business logic MUST have: `from synthorg.observability import get_logger` then `logger = get_logger(__name__)`. Never use import logging / logging.getLogger() / print() in application code.

Applied to files:

  • src/synthorg/settings/subscribers/observability_subscriber.py
  • src/synthorg/observability/sink_config_builder.py
📚 Learning: 2026-03-20T11:18:48.128Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T11:18:48.128Z
Learning: Applies to src/synthorg/**/*.py : Every module with business logic MUST have `from synthorg.observability import get_logger` followed by `logger = get_logger(__name__)`.

Applied to files:

  • src/synthorg/observability/sink_config_builder.py
📚 Learning: 2026-03-16T06:24:56.341Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T06:24:56.341Z
Learning: Applies to src/synthorg/**/*.py : Every module with business logic must have: `from synthorg.observability import get_logger` then `logger = get_logger(__name__)`

Applied to files:

  • src/synthorg/observability/sink_config_builder.py
📚 Learning: 2026-03-19T11:33:01.580Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T11:33:01.580Z
Learning: Applies to src/synthorg/**/*.py : Every module with business logic must import logger via `from synthorg.observability import get_logger` and initialize with `logger = get_logger(__name__)`

Applied to files:

  • src/synthorg/observability/sink_config_builder.py
📚 Learning: 2026-03-15T18:38:44.202Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:38:44.202Z
Learning: Applies to src/synthorg/**/*.py : Every module with business logic must import `from synthorg.observability import get_logger` and define `logger = get_logger(__name__)`

Applied to files:

  • src/synthorg/observability/sink_config_builder.py
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Applies to src/synthorg/**/*.py : Handle errors explicitly, never silently swallow. Validate at system boundaries (user input, external APIs, config files).

Applied to files:

  • src/synthorg/observability/sink_config_builder.py
📚 Learning: 2026-03-30T16:36:12.059Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-30T16:36:12.059Z
Learning: Applies to **/*.py : Validate: at system boundaries (user input, external APIs, config files)

Applied to files:

  • src/synthorg/observability/sink_config_builder.py
📚 Learning: 2026-03-16T20:14:00.937Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T20:14:00.937Z
Learning: Applies to **/*.py : Validate: at system boundaries (user input, external APIs, config files).

Applied to files:

  • src/synthorg/observability/sink_config_builder.py
🔇 Additional comments (20)
docs/design/operations.md (1)

1401-1418: Documentation is accurate and well-structured.

The runtime settings documentation correctly enumerates all four observability settings (root_log_level, enable_correlation, sink_overrides, custom_sinks) and accurately describes their JSON structure. The hot-reload trigger wording at lines 1415-1417 now correctly lists all four settings, addressing the previous review feedback.

src/synthorg/settings/subscribers/observability_subscriber.py (8)

1-24: Module setup is correct and follows project conventions.

The logger is properly initialized with get_logger(__name__) and event constants are imported from the domain-specific events module as required. Type checking import for SettingsService avoids circular imports.


26-35: Constants are well-defined and immutable.

Using frozenset for _WATCHED and _VALID_BOOL_STRINGS ensures immutability and efficient membership testing.


38-77: Class design is solid with clear documentation of failure modes.

The docstring at lines 45-48 honestly documents that configure_logging failure may leave the pipeline degraded since handlers are torn down before new ones are attached. The asyncio.Lock ensures serialized rebuilds for last-write-wins semantics.


92-100: Defensive guard for unexpected namespace is appropriate.

While the dispatcher filters by watched_keys before calling on_settings_changed (per src/synthorg/settings/dispatcher.py:206), this guard provides defense-in-depth against direct calls in tests or alternative dispatchers. The warning log correctly signals an unexpected condition.


105-115: Efficient parallel settings reads using asyncio.gather.

Reading all 4 settings concurrently minimizes latency during hot reload.


117-166: Validation and parsing logic is robust.

The enable_correlation validation at lines 137-146 now properly rejects malformed values (only accepting "true"/"false" strings), addressing the previous review feedback. The method correctly returns None on validation failures to preserve the existing logging configuration.


168-199: Failure handling is pragmatic given the non-atomic rebuild constraint.

The stderr fallback at lines 184-188 correctly uses sys.stderr.write() (addressing the past review about print()), ensuring some output reaches the operator even if logging is degraded. The dual-path approach (stderr + logger.error) maximizes the chance of surfacing the failure.

The past review noted that configure_logging() tears down handlers before rebuilding, leaving a degraded state on failure. The docstring at lines 45-48 honestly documents this limitation. A true atomic rebuild would require changes to the core logging setup, which is outside this PR's scope.


209-229: Pipeline rebuild orchestration is clean and follows fail-safe pattern.

The method correctly short-circuits on any failure, preserving the existing configuration. The separation of concerns (read → parse → apply) makes the flow easy to follow and test.

src/synthorg/observability/sink_config_builder.py (11)

1-34: Module setup addresses previous review feedback.

The module now includes logger = get_logger(__name__) at line 34, complying with the coding guideline that "Every module with business logic MUST have: from synthorg.observability import get_logger then logger = get_logger(name)".

The ruff: noqa: TRY004 comment at line 1 appropriately documents the deliberate choice to raise ValueError for all validation failures, providing a consistent public API contract.


36-65: Constants are well-organized with appropriate limits.

The field validation sets (_OVERRIDE_FIELDS, _CUSTOM_SINK_FIELDS, _ROTATION_FIELDS) enable strict unknown-field rejection, addressing the previous review feedback. The limits _MAX_CUSTOM_SINKS = 20 and _MAX_ROUTING_PREFIXES = 50 provide reasonable DoS protection.


67-79: SinkBuildResult correctly uses immutable types.

Using frozen=True dataclass with MappingProxyType for routing_overrides ensures the result cannot be accidentally mutated after construction.


84-105: Validation helpers enforce strict type checking.

_parse_bool at lines 96-105 now requires actual JSON booleans, addressing the past review concern about truthiness coercion (e.g., "enabled": "false" would have been truthy). _reject_unknown_fields ensures typos like "levle" are caught at validation time.


120-184: JSON parsing with comprehensive validation.

Both _parse_sink_overrides and _parse_custom_sinks now call _reject_unknown_fields, addressing the past review concern about silently accepting typos like {"levle": "debug"}. The maximum entries limit at line 171-173 prevents excessive resource consumption.


207-251: Rotation parsing addresses all previous review concerns.

  • Line 220-222: Type check ensures rotation is a dict, rejecting "rotation": "disabled" or []
  • Line 223: Unknown fields rejected via _reject_unknown_fields
  • Lines 238 and 245: The check isinstance(val, bool) specifically excludes booleans (since bool is a subclass of int in Python), preventing true from becoming 1

The commit message "reject non-int types for rotation fields (prevent bool/float acceptance)" confirms this was intentionally fixed.


256-301: Override application with proper type validation.

The enabled and json_format fields now use _parse_bool (lines 271-274 and 289-292), ensuring strings like "false" are rejected rather than being coerced to truthy values. The console sink protection at lines 276-280 correctly enforces the documented constraint.


306-350: Custom sink construction with proper defaults and validation.

The builder correctly delegates path safety validation (absolute paths, traversal) to SinkConfig's own validator (noted at line 342). Defaults are sensible: level=INFO, json_format=True, rotation=RotationConfig().


352-385: Routing prefix validation requires actual strings.

Line 380 now checks isinstance(prefix, str), addressing the past review concern about str(prefix) coercing non-strings like [true, 123, {}]. The commit message confirms "tighten type checks (routing prefixes must be str)".


411-484: Builder correctly prevents path collisions.

The check at lines 424-429 ensures custom sink file paths cannot collide with default sink paths, "reserved even if disabled" per the docstring. This matches the documentation in operations.md line 1418. Duplicate paths within custom_sinks are also caught at lines 430-435.

The main builder function build_log_config_from_settings has a clean flow: parse JSON inputs → merge defaults with overrides → process custom entries → construct immutable result.


34-34: 🧹 Nitpick | 🔵 Trivial

Logger is defined but unused in this pure-function module.

The logger is declared at line 34 but no logging calls exist in this module. Validation failures raise ValueError without logging first.

This is acceptable because ObservabilitySettingsSubscriber._parse_and_build (lines 148-166) catches all exceptions from build_log_config_from_settings and logs them with full context (subscriber, key, exc_info=True) before returning. The builder's role as a pure validation/transformation function is preserved, while the caller handles observability concerns.

⛔ Skipped due to learnings
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/**/*.py : Variable name for logger: always `logger` (not `_logger`, not `log`)
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T15:43:05.601Z
Learning: Applies to src/**/*.py : Always use logger variable name (not _logger or log)
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T06:30:14.180Z
Learning: Applies to src/synthorg/observability/**/*.py : Observability includes structured logging via `get_logger(__name__)`, correlation tracking, and log sinks.
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T11:33:01.580Z
Learning: Applies to src/synthorg/**/*.py : Every module with business logic must import logger via `from synthorg.observability import get_logger` and initialize with `logger = get_logger(__name__)`
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-30T16:36:12.059Z
Learning: Applies to src/synthorg/**/*.py : Every module with business logic MUST have: from synthorg.observability import get_logger then logger = get_logger(__name__)
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T11:33:01.580Z
Learning: Applies to src/synthorg/**/*.py : Always use variable name `logger` (not `_logger`, not `log`)
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T06:43:14.114Z
Learning: Applies to src/synthorg/**/*.py : Every module with business logic MUST have: `from synthorg.observability import get_logger` then `logger = get_logger(__name__)`. Never use `import logging` / `logging.getLogger()` / `print()` in application code. Variable name: always `logger`.
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:28:13.207Z
Learning: Applies to src/synthorg/**/*.py : Every module with business logic MUST have: `from synthorg.observability import get_logger` then `logger = get_logger(__name__)`. Never use `import logging` / `logging.getLogger()` / `print()` in application code. Variable name: always `logger` (not `_logger`, not `log`).
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-30T16:36:12.059Z
Learning: Applies to src/synthorg/**/*.py : Variable name: always logger (not _logger, not log)
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T06:24:56.341Z
Learning: Applies to src/synthorg/**/*.py : Always use `logger` as the variable name, not `_logger` or `log`

Walkthrough

Adds two observability settings, sink_overrides and custom_sinks, to support runtime editing of default sinks and adding file sinks. Introduces ObservabilitySettingsSubscriber to read observability settings and rebuild the logging pipeline on changes. Adds a pure sink_config_builder module that validates and merges JSON inputs into a LogConfig and produces routing overrides. Exports new observability event constants, exposes SINK_ROUTING, extends build_handler and configure_logging to accept routing overrides, and adds unit and integration tests for hot-reload and validation.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 23.33% which is insufficient. The required threshold is 40.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: runtime sink configuration via SettingsService, which is the primary objective of this PR.
Description check ✅ Passed The description comprehensively outlines the changes including settings additions, subscriber implementation, routing extensions, and security fix, directly relating to the changeset.
Linked Issues check ✅ Passed The PR successfully implements all acceptance criteria from #564: runtime enable/disable, level changes, hot reload via subscriber, console sink protection, path validation, and comprehensive test coverage (44+16+6 tests).
Out of Scope Changes check ✅ Passed All changes align with #564 scope: observability settings expansion, subscriber implementation, routing extensions, and security fix for MSW origin check are all within stated objectives.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

@Aureliolo Aureliolo temporarily deployed to cloudflare-preview March 30, 2026 16:54 — with GitHub Actions Inactive
Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request enables hot-reloading of the logging pipeline by introducing sink_overrides and custom_sinks settings. It adds an ObservabilitySettingsSubscriber to react to configuration changes and a sink_config_builder to merge default sinks with runtime overrides. Critical feedback was provided regarding the use of legacy Python 2 exception handling syntax in the new subscriber, which will cause SyntaxError in a Python 3 environment. Additionally, a minor simplification was suggested for the routing overrides dictionary conversion.

"observability",
"custom_sinks",
)
except MemoryError, RecursionError:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

This except syntax is for Python 2. In a Python 3 environment, which this project appears to be using, this will raise a SyntaxError. Multiple exceptions should be caught as a tuple.

        except (MemoryError, RecursionError):

# Parse root level separately for accurate error reporting.
try:
root_level = LogLevel(root_level_result.value.upper())
except ValueError, AttributeError:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

This except syntax is for Python 2. In a Python 3 environment, which this project appears to be using, this will raise a SyntaxError. Multiple exceptions should be caught as a tuple.

        except (ValueError, AttributeError):

custom_sinks_json=custom_result.value,
log_dir=self._log_dir,
)
except MemoryError, RecursionError:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

This except syntax is for Python 2. In a Python 3 environment, which this project appears to be using, this will raise a SyntaxError. Multiple exceptions should be caught as a tuple.

        except (MemoryError, RecursionError):

build_result.config,
routing_overrides=dict(build_result.routing_overrides) or None,
)
except MemoryError, RecursionError:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

This except syntax is for Python 2. In a Python 3 environment, which this project appears to be using, this will raise a SyntaxError. Multiple exceptions should be caught as a tuple.

        except (MemoryError, RecursionError):

try:
configure_logging(
build_result.config,
routing_overrides=dict(build_result.routing_overrides) or None,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The or None here is redundant and can make the code slightly harder to read. The configure_logging function and its helper _attach_handlers can correctly handle an empty dictionary for routing_overrides. Simplifying this will improve clarity.

                routing_overrides=dict(build_result.routing_overrides),

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 30, 2026

Dependency Review

✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.

Snapshot Warnings

⚠️: No snapshots were found for the head SHA e319311.
Ensure that dependencies are being submitted on PR branches. Re-running this action after a short time may resolve the issue. See the documentation for more information and troubleshooting advice.

Scanned Files

None

@codecov
Copy link
Copy Markdown

codecov bot commented Mar 30, 2026

Codecov Report

❌ Patch coverage is 99.54128% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 92.20%. Comparing base (9993088) to head (e319311).
⚠️ Report is 1 commits behind head on main.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
src/synthorg/observability/sink_config_builder.py 99.34% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #934      +/-   ##
==========================================
+ Coverage   92.15%   92.20%   +0.04%     
==========================================
  Files         596      598       +2     
  Lines       31541    31720     +179     
  Branches     3059     3088      +29     
==========================================
+ Hits        29068    29248     +180     
+ Misses       1950     1949       -1     
  Partials      523      523              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/synthorg/observability/sink_config_builder.py`:
- Around line 144-184: The parser _parse_rotation_override currently assumes raw
is a dict and probes keys; add an explicit type check at the top of
_parse_rotation_override (e.g., if not isinstance(raw, dict): raise
ValueError("rotation must be an object/dict")) so non-object JSON values
(strings, lists, numbers) fail validation instead of silently reusing the base
RotationConfig; then apply the same guard in the other rotation-parsing sites
noted (the blocks around the other occurrences at the referenced ranges) so each
parsing path validates that the rotation override is a mapping before accessing
keys like "strategy", "max_bytes", or "backup_count".
- Around line 203-215: The code currently coerces non-boolean JSON values via
truthiness; change the handling of override["enabled"] and
override["json_format"] to require actual booleans: when "enabled" is present,
first check isinstance(override["enabled"], bool) and raise ValueError if not a
bool, then if override["enabled"] is False check identifier == _CONSOLE_ID and
raise the existing error or return None; when "json_format" is present check
isinstance(override["json_format"], bool) and raise ValueError if not, then set
updates["json_format"] = override["json_format"]. Apply the same explicit bool
validation to the other occurrence referenced (around lines 250-251) so both
sites validate types rather than coercing truthiness.
- Around line 74-101: The code currently checks sink identifier names against
_VALID_OVERRIDE_KEYS instead of validating the keys inside each override dict,
so unknown inner fields are silently accepted; in _parse_sink_overrides, change
the validation to iterate over value (the override dict) and ensure each
inner_field is in _VALID_OVERRIDE_KEYS (raise ValueError listing the offending
field and allowed keys) and keep the existing type checks; apply the same
pattern in the corresponding parser for custom sinks (e.g., _parse_custom_sinks)
by validating each custom sink's dict keys against _VALID_CUSTOM_SINK_KEYS and
raising on unknown fields.
- Around line 18-30: Add module-level logger by importing get_logger from
synthorg.observability and defining logger = get_logger(__name__) at top of
sink_config_builder.py, then instrument all validation failure paths in
functions such as build_log_config_from_settings and any helpers used by
ObservabilitySettingsSubscriber to log context and error details (use
logger.warning or logger.error as appropriate) immediately before each raise so
the warning/error contains the failing setting name/value and the reason for
failure; ensure messages are clear and include the exception or validation
message where available.

In `@src/synthorg/settings/definitions/observability.py`:
- Around line 35-68: The two SettingDefinition registrations for sink_overrides
and custom_sinks are missing yaml_path so YAML-configured values aren’t
considered; update the SettingDefinition calls for the keys "sink_overrides" and
"custom_sinks" (the _r.register(...) blocks where
namespace=SettingNamespace.OBSERVABILITY) to include a yaml_path parameter (e.g.
"observability.sink_overrides" and "observability.custom_sinks") so settings
resolution follows DB > env > YAML > code defaults.

In `@src/synthorg/settings/subscribers/observability_subscriber.py`:
- Around line 158-179: The try/except currently lets configure_logging clear the
live handlers before failing, so save the existing logging pipeline first and
restore it on non-fatal exceptions: before calling configure_logging in
observability_subscriber.py (the block that logs
SETTINGS_OBSERVABILITY_REBUILD_FAILED), snapshot the current logger/handlers (or
call a restore helper) then attempt configure_logging(build_result.config,
routing_overrides=...). On MemoryError or RecursionError re-raise as before; on
any other Exception restore the saved handlers/pipeline, then emit the
SETTINGS_OBSERVABILITY_REBUILD_FAILED error (including exc_info) and return —
this yields a rollback path instead of leaving logging degraded. Ensure you
reference configure_logging and the error handling around
SETTINGS_OBSERVABILITY_REBUILD_FAILED/subscriber_name/key when implementing the
restore.
- Around line 123-135: The current code silently coerces enable_correlation
using str(correlation_result.value).lower() == "true", which treats any
malformed value as False; instead validate and reject invalid values the same
way as root_level parsing does: attempt to parse correlation_result.value as a
boolean (explicitly accept only "true" or "false" strings or proper bool types),
and on invalid input log SETTINGS_OBSERVABILITY_VALIDATION_FAILED with
subscriber=self.subscriber_name, key=key and exc_info=True and return; mirror
the error handling used around LogLevel(root_level_result.value.upper())
(references: enable_correlation, correlation_result, root_level_result,
LogLevel, root_level, SETTINGS_OBSERVABILITY_VALIDATION_FAILED).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 122b8e1c-96be-4c6b-9a67-080ba74642a8

📥 Commits

Reviewing files that changed from the base of the PR and between f5cfe07 and d33f5f6.

📒 Files selected for processing (14)
  • docs/design/operations.md
  • src/synthorg/api/app.py
  • src/synthorg/observability/events/settings.py
  • src/synthorg/observability/setup.py
  • src/synthorg/observability/sink_config_builder.py
  • src/synthorg/observability/sinks.py
  • src/synthorg/settings/definitions/observability.py
  • src/synthorg/settings/subscribers/__init__.py
  • src/synthorg/settings/subscribers/observability_subscriber.py
  • tests/integration/observability/test_runtime_sink_config_integration.py
  • tests/unit/observability/test_sink_config_builder.py
  • tests/unit/observability/test_sink_routing.py
  • tests/unit/settings/test_observability_subscriber.py
  • web/public/mockServiceWorker.js
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
  • GitHub Check: Deploy Preview
  • GitHub Check: Dashboard Test
  • GitHub Check: Test (Python 3.14)
  • GitHub Check: Build Backend
  • GitHub Check: Build Web
  • GitHub Check: Build Sandbox
  • GitHub Check: Dependency Review
  • GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (5)
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.py: No from __future__ import annotations—Python 3.14 has PEP 649
PEP 758 except syntax: use except A, B: (no parentheses)—ruff enforces this on Python 3.14
Type hints: all public functions, mypy strict mode
Docstrings: Google style, required on public classes/functions (enforced by ruff D rules)
Immutability: create new objects, never mutate existing ones. For non-Pydantic internal collections (registries, BaseTool), use copy.deepcopy() at construction + MappingProxyType wrapping for read-only enforcement
Config vs runtime state: frozen Pydantic models for config/identity; separate mutable-via-copy models (using model_copy(update=...)) for runtime state that evolves. Never mix static config fields with mutable runtime fields in one model
Models: Pydantic v2 (BaseModel, model_validator, computed_field, ConfigDict). Adopted conventions: use allow_inf_nan=False in all ConfigDict declarations; use @computed_field for derived values; use NotBlankStr for all identifier/name fields
Async concurrency: prefer asyncio.TaskGroup for fan-out/fan-in parallel operations in new code; prefer structured concurrency over bare create_task
Line length: 88 characters (ruff)
Functions: < 50 lines, files < 800 lines
Errors: handle explicitly, never silently swallow
Validate: at system boundaries (user input, external APIs, config files)

Files:

  • src/synthorg/settings/subscribers/__init__.py
  • tests/unit/observability/test_sink_routing.py
  • src/synthorg/api/app.py
  • src/synthorg/observability/setup.py
  • src/synthorg/observability/sinks.py
  • tests/unit/settings/test_observability_subscriber.py
  • src/synthorg/observability/events/settings.py
  • src/synthorg/settings/subscribers/observability_subscriber.py
  • tests/unit/observability/test_sink_config_builder.py
  • src/synthorg/observability/sink_config_builder.py
  • tests/integration/observability/test_runtime_sink_config_integration.py
  • src/synthorg/settings/definitions/observability.py
src/synthorg/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

src/synthorg/**/*.py: Every module with business logic MUST have: from synthorg.observability import get_logger then logger = get_logger(name)
Variable name: always logger (not _logger, not log)
Event names: always use constants from domain-specific modules under synthorg.observability.events; import directly from synthorg.observability.events.
Structured kwargs: always logger.info(EVENT, key=value)—never logger.info('msg %s', val)
All error paths must log at WARNING or ERROR with context before raising
All state transitions must log at INFO
DEBUG for object creation, internal flow, entry/exit of key functions
All provider calls go through BaseCompletionProvider which applies retry + rate limiting automatically; never implement retry logic in driver subclasses or calling code

Files:

  • src/synthorg/settings/subscribers/__init__.py
  • src/synthorg/api/app.py
  • src/synthorg/observability/setup.py
  • src/synthorg/observability/sinks.py
  • src/synthorg/observability/events/settings.py
  • src/synthorg/settings/subscribers/observability_subscriber.py
  • src/synthorg/observability/sink_config_builder.py
  • src/synthorg/settings/definitions/observability.py
src/synthorg/**/!(setup|sinks).py

📄 CodeRabbit inference engine (CLAUDE.md)

Never use import logging / logging.getLogger() / print() in application code; exception: observability/setup.py and observability/sinks.py may use stdlib logging and print() for bootstrap code

Files:

  • src/synthorg/settings/subscribers/__init__.py
  • src/synthorg/api/app.py
  • src/synthorg/observability/events/settings.py
  • src/synthorg/settings/subscribers/observability_subscriber.py
  • src/synthorg/observability/sink_config_builder.py
  • src/synthorg/settings/definitions/observability.py
**/*.{py,ts,tsx,md}

📄 CodeRabbit inference engine (CLAUDE.md)

Vendor-agnostic everywhere: NEVER use real vendor names (Anthropic, OpenAI, Claude, GPT, etc.) in project-owned code, docstrings, comments, tests, or config examples; use generic names: example-provider, example-large-001, etc.; vendor names only in: design spec, .claude/ files, third-party imports, provider presets

Files:

  • src/synthorg/settings/subscribers/__init__.py
  • tests/unit/observability/test_sink_routing.py
  • src/synthorg/api/app.py
  • src/synthorg/observability/setup.py
  • docs/design/operations.md
  • src/synthorg/observability/sinks.py
  • tests/unit/settings/test_observability_subscriber.py
  • src/synthorg/observability/events/settings.py
  • src/synthorg/settings/subscribers/observability_subscriber.py
  • tests/unit/observability/test_sink_config_builder.py
  • src/synthorg/observability/sink_config_builder.py
  • tests/integration/observability/test_runtime_sink_config_integration.py
  • src/synthorg/settings/definitions/observability.py
tests/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

tests/**/*.py: Markers: @pytest.mark.unit, @pytest.mark.integration, @pytest.mark.e2e, @pytest.mark.slow
Async: asyncio_mode = 'auto'—no manual @pytest.mark.asyncio needed
Timeout: 30 seconds per test (global in pyproject.toml); non-default overrides like timeout(60) are allowed
Parametrize: Prefer @pytest.mark.parametrize for testing similar cases
Property-based testing: Python uses Hypothesis (@given + @settings); Hypothesis profiles: ci (50 examples, default) and dev (1000 examples); run dev profile: HYPOTHESIS_PROFILE=dev uv run python -m pytest tests/ -m unit -n auto -k properties
Flaky tests: NEVER skip, dismiss, or ignore flaky tests—always fix them fully and fundamentally; for timing-sensitive tests, mock time.monotonic() and asyncio.sleep(); for tasks that must block indefinitely, use asyncio.Event().wait()

Files:

  • tests/unit/observability/test_sink_routing.py
  • tests/unit/settings/test_observability_subscriber.py
  • tests/unit/observability/test_sink_config_builder.py
  • tests/integration/observability/test_runtime_sink_config_integration.py
🧠 Learnings (17)
📓 Common learnings
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/settings/**/*.py : Settings package (settings/): runtime-editable settings persistence (DB > env > YAML > code defaults), typed definitions (9 namespaces), Fernet encryption for sensitive values, config bridge (JSON serialization for Pydantic/collections), ConfigResolver (typed accessors), validation, registry, change notifications via message bus, SettingsSubscriber protocol, SettingsChangeDispatcher (polls `#settings` channel, routes to subscribers, restart_required filtering)
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T06:30:14.180Z
Learning: Applies to src/synthorg/observability/**/*.py : Observability includes structured logging via `get_logger(__name__)`, correlation tracking, and log sinks.
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T06:24:56.341Z
Learning: Applies to src/synthorg/observability/**/*.py : Observability must use structured logging with correlation tracking and log sinks
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Settings: Runtime-editable settings persistence (DB > env > YAML > code defaults), typed definitions (9 namespaces), Fernet encryption for sensitive values, config bridge, ConfigResolver (typed composed reads for controllers), validation, registry, change notifications via message bus. Per-namespace setting definitions in definitions/ submodule (api, company, providers, memory, budget, security, coordination, observability, backup).
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/observability/**/*.py : Observability package (observability/): structured logging, correlation tracking, log sinks; event constants organized by domain under observability/events/ (e.g., events.api, events.tool, events.git, events.context_budget, events.backup)
📚 Learning: 2026-03-30T16:36:12.059Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-30T16:36:12.059Z
Learning: Applies to src/synthorg/**/*.py : Event names: always use constants from domain-specific modules under synthorg.observability.events; import directly from synthorg.observability.events.<domain>

Applied to files:

  • src/synthorg/settings/subscribers/__init__.py
  • src/synthorg/observability/events/settings.py
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/settings/**/*.py : Settings package (settings/): runtime-editable settings persistence (DB > env > YAML > code defaults), typed definitions (9 namespaces), Fernet encryption for sensitive values, config bridge (JSON serialization for Pydantic/collections), ConfigResolver (typed accessors), validation, registry, change notifications via message bus, SettingsSubscriber protocol, SettingsChangeDispatcher (polls `#settings` channel, routes to subscribers, restart_required filtering)

Applied to files:

  • src/synthorg/settings/subscribers/__init__.py
  • src/synthorg/api/app.py
  • docs/design/operations.md
  • tests/unit/settings/test_observability_subscriber.py
  • src/synthorg/observability/events/settings.py
  • src/synthorg/settings/subscribers/observability_subscriber.py
  • src/synthorg/settings/definitions/observability.py
📚 Learning: 2026-03-18T21:23:23.586Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-18T21:23:23.586Z
Learning: Applies to src/synthorg/**/*.py : Event names: always use constants from the domain-specific module under synthorg.observability.events (e.g., API_REQUEST_STARTED from events.api, TOOL_INVOKE_START from events.tool). Import directly from synthorg.observability.events.<domain>.

Applied to files:

  • src/synthorg/settings/subscribers/__init__.py
  • src/synthorg/observability/events/settings.py
📚 Learning: 2026-03-15T18:38:44.202Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:38:44.202Z
Learning: Applies to src/synthorg/**/*.py : Every module with business logic must import `from synthorg.observability import get_logger` and define `logger = get_logger(__name__)`

Applied to files:

  • src/synthorg/settings/subscribers/__init__.py
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/observability/**/*.py : Observability package (observability/): structured logging, correlation tracking, log sinks; event constants organized by domain under observability/events/ (e.g., events.api, events.tool, events.git, events.context_budget, events.backup)

Applied to files:

  • src/synthorg/settings/subscribers/__init__.py
  • src/synthorg/api/app.py
  • src/synthorg/observability/setup.py
  • docs/design/operations.md
  • src/synthorg/observability/sinks.py
  • src/synthorg/observability/events/settings.py
  • src/synthorg/settings/subscribers/observability_subscriber.py
  • tests/unit/observability/test_sink_config_builder.py
  • src/synthorg/observability/sink_config_builder.py
  • tests/integration/observability/test_runtime_sink_config_integration.py
  • src/synthorg/settings/definitions/observability.py
📚 Learning: 2026-03-15T18:38:44.202Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:38:44.202Z
Learning: Applies to src/synthorg/**/*.py : Always use event name constants from domain-specific modules under `synthorg.observability.events` (e.g., `PROVIDER_CALL_START` from `events.provider`); import directly: `from synthorg.observability.events.<domain> import EVENT_CONSTANT`

Applied to files:

  • src/synthorg/settings/subscribers/__init__.py
  • src/synthorg/observability/events/settings.py
📚 Learning: 2026-03-20T11:18:48.128Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T11:18:48.128Z
Learning: Applies to src/synthorg/**/*.py : Use event name constants from domain-specific modules under `synthorg.observability.events` (e.g., `API_REQUEST_STARTED` from `events.api`, `TOOL_INVOKE_START` from `events.tool`). Import directly: `from synthorg.observability.events.<domain> import EVENT_CONSTANT`.

Applied to files:

  • src/synthorg/settings/subscribers/__init__.py
  • src/synthorg/observability/events/settings.py
📚 Learning: 2026-03-17T06:30:14.180Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T06:30:14.180Z
Learning: Applies to src/synthorg/observability/**/*.py : Observability includes structured logging via `get_logger(__name__)`, correlation tracking, and log sinks.

Applied to files:

  • src/synthorg/settings/subscribers/__init__.py
  • tests/unit/observability/test_sink_routing.py
  • src/synthorg/api/app.py
  • src/synthorg/observability/setup.py
  • docs/design/operations.md
  • src/synthorg/observability/sinks.py
  • src/synthorg/settings/subscribers/observability_subscriber.py
  • tests/unit/observability/test_sink_config_builder.py
  • src/synthorg/observability/sink_config_builder.py
  • tests/integration/observability/test_runtime_sink_config_integration.py
  • src/synthorg/settings/definitions/observability.py
📚 Learning: 2026-03-15T19:14:27.144Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T19:14:27.144Z
Learning: Applies to src/synthorg/**/*.py : Use event name constants from synthorg.observability.events domain-specific modules (e.g., PROVIDER_CALL_START from events.provider). Import directly: from synthorg.observability.events.<domain> import EVENT_CONSTANT.

Applied to files:

  • src/synthorg/settings/subscribers/__init__.py
  • src/synthorg/observability/events/settings.py
📚 Learning: 2026-03-20T21:44:04.528Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T21:44:04.528Z
Learning: Applies to src/synthorg/**/*.py : Always use event name constants from domain-specific modules under `synthorg.observability.events` (e.g., `API_REQUEST_STARTED` from `events.api`, `TOOL_INVOKE_START` from `events.tool`); import directly rather than using string literals

Applied to files:

  • src/synthorg/settings/subscribers/__init__.py
  • src/synthorg/observability/events/settings.py
📚 Learning: 2026-03-16T06:24:56.341Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T06:24:56.341Z
Learning: Applies to src/synthorg/observability/**/*.py : Observability must use structured logging with correlation tracking and log sinks

Applied to files:

  • tests/unit/observability/test_sink_routing.py
  • src/synthorg/observability/setup.py
  • docs/design/operations.md
  • src/synthorg/observability/sinks.py
  • src/synthorg/settings/subscribers/observability_subscriber.py
  • tests/unit/observability/test_sink_config_builder.py
  • src/synthorg/observability/sink_config_builder.py
  • tests/integration/observability/test_runtime_sink_config_integration.py
  • src/synthorg/settings/definitions/observability.py
📚 Learning: 2026-03-30T16:36:12.059Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-30T16:36:12.059Z
Learning: Applies to src/synthorg/**/!(setup|sinks).py : Never use import logging / logging.getLogger() / print() in application code; exception: observability/setup.py and observability/sinks.py may use stdlib logging and print() for bootstrap code

Applied to files:

  • src/synthorg/observability/setup.py
📚 Learning: 2026-03-17T06:30:14.180Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T06:30:14.180Z
Learning: Applies to src/synthorg/settings/**/*.py : Settings use runtime-editable persistence with precedence: DB > env > YAML > code defaults. 8 namespaces with Fernet encryption for sensitive values.

Applied to files:

  • docs/design/operations.md
  • src/synthorg/settings/definitions/observability.py
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Settings: Runtime-editable settings persistence (DB > env > YAML > code defaults), typed definitions (9 namespaces), Fernet encryption for sensitive values, config bridge, ConfigResolver (typed composed reads for controllers), validation, registry, change notifications via message bus. Per-namespace setting definitions in definitions/ submodule (api, company, providers, memory, budget, security, coordination, observability, backup).

Applied to files:

  • docs/design/operations.md
📚 Learning: 2026-03-15T18:28:13.207Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:28:13.207Z
Learning: Applies to src/synthorg/**/*.py : Event names: always use constants from domain-specific modules under synthorg.observability.events (e.g., PROVIDER_CALL_START from events.provider, BUDGET_RECORD_ADDED from events.budget, etc.). Import directly: `from synthorg.observability.events.<domain> import EVENT_CONSTANT`.

Applied to files:

  • src/synthorg/observability/events/settings.py
📚 Learning: 2026-03-16T06:24:56.341Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T06:24:56.341Z
Learning: Applies to src/synthorg/**/*.py : Always use event name constants from the domain-specific module under `synthorg.observability.events` in logging calls

Applied to files:

  • src/synthorg/observability/events/settings.py
🔇 Additional comments (8)
web/public/mockServiceWorker.js (1)

24-27: Good same-origin guard placement.

This early return correctly blocks cross-origin message events before client resolution and command handling, which addresses the postMessage validation risk.

src/synthorg/settings/subscribers/__init__.py (1)

9-11: Subscriber export wiring looks correct.

ObservabilitySettingsSubscriber is properly imported and re-exported, matching the dispatcher integration pattern.

Also applies to: 19-19

tests/unit/observability/test_sink_routing.py (1)

7-7: Good move to public routing constant usage in tests.

These assertions now target SINK_ROUTING (public API) instead of a private implementation detail.

Also applies to: 70-71, 74-75, 80-81, 86-87, 115-115, 118-118, 130-130

src/synthorg/api/app.py (1)

89-90: Runtime observability subscriber wiring is correct.

Including ObservabilitySettingsSubscriber in dispatcher subscriptions with explicit log_dir fallback is solid.

Also applies to: 747-753

docs/design/operations.md (1)

1401-1411: Docs update is clear and implementation-aligned.

The new runtime sink settings and hot-reload semantics are documented accurately and concretely.

Also applies to: 1415-1417

src/synthorg/observability/setup.py (1)

11-11: Routing override plumbing is well integrated.

The routing_overrides threading and default-routing merge behavior are clean and maintain idempotent setup behavior.

Also applies to: 19-19, 180-204, 212-212, 323-327, 340-342, 371-376

src/synthorg/observability/events/settings.py (1)

35-48: New settings observability event constants look good.

Names follow the existing event taxonomy and improve subscriber observability coverage.

src/synthorg/observability/sinks.py (1)

56-56: Public routing table + injectable routing in handler factory is a solid change.

This keeps default routing intact while enabling controlled runtime overrides.

Also applies to: 206-207, 219-221, 226-226, 236-239

Comment on lines +18 to +30
import json
from dataclasses import dataclass
from types import MappingProxyType
from typing import Any

from synthorg.observability.config import (
DEFAULT_SINKS,
LogConfig,
RotationConfig,
SinkConfig,
)
from synthorg.observability.enums import LogLevel, RotationStrategy, SinkType

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Add the module logger and log validation failures before raising.

This is business logic under src/synthorg/**, but the module never defines logger = get_logger(__name__) and every validation path below raises silently. ObservabilitySettingsSubscriber wraps one call site, but build_log_config_from_settings() is also called directly and future callers will miss the required warning/error event. As per coding guidelines, "Every module with business logic MUST have: from synthorg.observability import get_logger then logger = get_logger(name)" and "All error paths must log at WARNING or ERROR with context before raising."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/synthorg/observability/sink_config_builder.py` around lines 18 - 30, Add
module-level logger by importing get_logger from synthorg.observability and
defining logger = get_logger(__name__) at top of sink_config_builder.py, then
instrument all validation failure paths in functions such as
build_log_config_from_settings and any helpers used by
ObservabilitySettingsSubscriber to log context and error details (use
logger.warning or logger.error as appropriate) immediately before each raise so
the warning/error contains the failing setting name/value and the reason for
failure; ensure messages are clear and include the exception or validation
message where available.

Comment on lines +158 to +179
# Rebuild the logging pipeline. configure_logging is not atomic:
# it clears old handlers before attaching new ones, so a failure
# here may leave the pipeline degraded.
try:
configure_logging(
build_result.config,
routing_overrides=dict(build_result.routing_overrides) or None,
)
except MemoryError, RecursionError:
raise
except Exception:
logger.error(
SETTINGS_OBSERVABILITY_REBUILD_FAILED,
subscriber=self.subscriber_name,
key=key,
note=(
"configure_logging failed -- old pipeline was already "
"torn down; logging may be degraded"
),
exc_info=True,
)
return
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

A failed rebuild still tears down the last known-good pipeline.

configure_logging() clears the existing handlers before attaching the new set (see src/synthorg/observability/setup.py:323-378), so an exception here leaves logging degraded even though this method claims to preserve the current configuration. Hot reload needs a staged reattach or a rollback to the last successful config/handler set.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/synthorg/settings/subscribers/observability_subscriber.py` around lines
158 - 179, The try/except currently lets configure_logging clear the live
handlers before failing, so save the existing logging pipeline first and restore
it on non-fatal exceptions: before calling configure_logging in
observability_subscriber.py (the block that logs
SETTINGS_OBSERVABILITY_REBUILD_FAILED), snapshot the current logger/handlers (or
call a restore helper) then attempt configure_logging(build_result.config,
routing_overrides=...). On MemoryError or RecursionError re-raise as before; on
any other Exception restore the saved handlers/pipeline, then emit the
SETTINGS_OBSERVABILITY_REBUILD_FAILED error (including exc_info) and return —
this yields a rollback path instead of leaving logging degraded. Ensure you
reference configure_logging and the error handling around
SETTINGS_OBSERVABILITY_REBUILD_FAILED/subscriber_name/key when implementing the
restore.

Aureliolo and others added 4 commits March 30, 2026 19:30
Add per-sink enable/disable, level/format overrides, rotation config,
and custom sink creation -- all hot-reloadable without restart via
ObservabilitySettingsSubscriber. Also fix MSW postMessage origin check
(CodeQL alert #149, CWE-020/CWE-940).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Pre-reviewed by 4 agents, 13 findings addressed:
- Wrap SinkBuildResult.routing_overrides with MappingProxyType
- Re-raise MemoryError/RecursionError in all except blocks
- Split LogLevel parse from sink config parse for accurate error msgs
- Widen configure_logging catch and fix misleading error message
- Add contextual error messages for int() rotation field conversion
- Validate file_path is a non-empty string (reject null/int/bool)
- Add 8 missing edge case tests (rotation strategy, routing type,
  multiple simultaneous overrides, custom rotation, log_dir, etc.)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Convert TypeError to ValueError in sink_config_builder for consistent
  public API contract (callers use `except ValueError`)
- Require actual JSON booleans for enabled/json_format (reject string
  "false" which is truthy in Python)
- Add type check in _parse_level for non-string JSON values
- Validate unknown fields in sink_overrides, custom_sinks, and rotation
  dicts (reject typos like "levle" instead of "level")
- Add rotation type check (reject "rotation": "disabled")
- Validate enable_correlation against {"true", "false"} instead of
  silently coercing any non-"true" to False
- Add asyncio.Lock for serializing concurrent pipeline rebuilds
- Use asyncio.gather for parallel settings reads
- Extract helpers to satisfy <50 line function limit
- Add stderr fallback for configure_logging failure path
- Accept Mapping type for routing_overrides (eliminate MappingProxyType
  -> dict -> MappingProxyType round-trip)
- Add yaml_path for sink_overrides and custom_sinks settings
- Remove dead SETTINGS_OBSERVABILITY_CONSOLE_PROTECTED event constant
- Fix docstrings (preservation claim, _parse_level case docs, concurrency)
- Add library reference for sink_config_builder module
- Reduce app.py from 810 to 789 lines (inline _build_bridge, compact comment)
- Cap custom_sinks at 20 entries, routing_prefixes at 50
- Use cast() instead of type: ignore[assignment]
- Add 28 new tests: strict bool validation, unknown field rejection,
  limits, enable_correlation validation, MemoryError/RecursionError
  propagation, rebuild lock

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

♻️ Duplicate comments (1)
src/synthorg/settings/subscribers/observability_subscriber.py (1)

175-200: ⚠️ Potential issue | 🟠 Major

A failed hot reload still tears down the live pipeline.

configure_logging() clears the root handlers before attaching the new ones. If a sink init fails here, this catch only reports the failure after teardown, so the process can be left with degraded/no logging instead of rolling back to the last known-good handler set. That still misses the safe reattach requirement from the PR objective.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/synthorg/settings/subscribers/observability_subscriber.py` around lines
175 - 200, The try/except around configure_logging allows the old logging
pipeline to be torn down before a failing reconfigure can roll back; change the
flow in observability_subscriber.py so configure_logging is applied to a
temporary/new handler set and only swapped into the root loggers on success (or
restore the previous handlers on exception). Specifically, update the code
around the configure_logging call so it does not clear root handlers in-place:
call a non-destructive initialization path or build the new handlers first, then
atomically replace the root handlers (or reattach the saved old handlers on any
Exception) before logging SETTINGS_OBSERVABILITY_REBUILD_FAILED using
self.subscriber_name and key; ensure MemoryError/RecursionError still propagate.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/design/operations.md`:
- Around line 1415-1417: Update the sentence describing hot-reload behavior to
reflect that ObservabilitySettingsSubscriber triggers a full logging pipeline
rebuild via configure_logging() not only when sink settings change but also when
root_log_level or enable_correlation change; mention all three triggers (sink
settings, root_log_level, enable_correlation) and keep the note that
configure_logging() is idempotent and custom sink file paths are reserved even
if disabled.

In `@src/synthorg/observability/sink_config_builder.py`:
- Around line 381-390: The loop that builds prefixes currently calls str(prefix)
which allows non-string JSON values to pass; change the validation in the
routing_prefixes handling (the loop over raw that builds prefixes) to require
each entry be an instance of str and non-empty after stripping: if not
isinstance(prefix, str) or not prefix.strip(): raise ValueError with the same
contextual message referencing routing_prefixes[{i}] and file_path; only then
append the stripped string to prefixes. This enforces that routing_prefixes
entries are actual non-empty strings.
- Around line 236-251: The code currently coerces values for raw["max_bytes"]
and raw["backup_count"] using int(...), which accepts booleans and truncates
floats; instead validate types and reject non-integers: for both "max_bytes" and
"backup_count" in raw, fetch value = raw["..."], ensure type(value) is int (or
use isinstance(value, int) and explicitly exclude bool), and if not raise
ValueError with the existing error message; only assign updates["max_bytes"] or
updates["backup_count"] when the value is a true integer.

In `@src/synthorg/settings/subscribers/observability_subscriber.py`:
- Around line 184-189: Replace the emergency fallback print() in
observability_subscriber.py (inside the configure_logging hot-reload path where
key is logged) with a direct sys.stderr.write() call and an explicit
sys.stderr.flush() (or call the project's bootstrap helper if available),
constructing the same message string (including key!r) and writing it to stderr
so it conforms to the rule banning print() outside observability/setup.py and
observability/sinks.py; ensure you remove the print(...) and use
sys.stderr.write(message + "\n") then sys.stderr.flush() to preserve
newline/flush behavior.

---

Duplicate comments:
In `@src/synthorg/settings/subscribers/observability_subscriber.py`:
- Around line 175-200: The try/except around configure_logging allows the old
logging pipeline to be torn down before a failing reconfigure can roll back;
change the flow in observability_subscriber.py so configure_logging is applied
to a temporary/new handler set and only swapped into the root loggers on success
(or restore the previous handlers on exception). Specifically, update the code
around the configure_logging call so it does not clear root handlers in-place:
call a non-destructive initialization path or build the new handlers first, then
atomically replace the root handlers (or reattach the saved old handlers on any
Exception) before logging SETTINGS_OBSERVABILITY_REBUILD_FAILED using
self.subscriber_name and key; ensure MemoryError/RecursionError still propagate.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: a86df6f5-10ad-4347-88e9-9b03920aaf31

📥 Commits

Reviewing files that changed from the base of the PR and between d33f5f6 and 97042dc.

📒 Files selected for processing (15)
  • docs/api/observability.md
  • docs/design/operations.md
  • src/synthorg/api/app.py
  • src/synthorg/observability/events/settings.py
  • src/synthorg/observability/setup.py
  • src/synthorg/observability/sink_config_builder.py
  • src/synthorg/observability/sinks.py
  • src/synthorg/settings/definitions/observability.py
  • src/synthorg/settings/subscribers/__init__.py
  • src/synthorg/settings/subscribers/observability_subscriber.py
  • tests/integration/observability/test_runtime_sink_config_integration.py
  • tests/unit/observability/test_sink_config_builder.py
  • tests/unit/observability/test_sink_routing.py
  • tests/unit/settings/test_observability_subscriber.py
  • web/public/mockServiceWorker.js
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: Dashboard Test
  • GitHub Check: Test (Python 3.14)
  • GitHub Check: Build Backend
  • GitHub Check: Build Web
  • GitHub Check: Dependency Review
  • GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{py,ts,tsx,md}

📄 CodeRabbit inference engine (CLAUDE.md)

Vendor-agnostic everywhere: NEVER use real vendor names (Anthropic, OpenAI, Claude, GPT, etc.) in project-owned code, docstrings, comments, tests, or config examples; use generic names: example-provider, example-large-001, etc.; vendor names only in: design spec, .claude/ files, third-party imports, provider presets

Files:

  • docs/api/observability.md
  • tests/unit/observability/test_sink_routing.py
  • src/synthorg/settings/subscribers/__init__.py
  • src/synthorg/api/app.py
  • src/synthorg/observability/setup.py
  • src/synthorg/observability/events/settings.py
  • docs/design/operations.md
  • src/synthorg/observability/sinks.py
  • tests/unit/settings/test_observability_subscriber.py
  • src/synthorg/settings/definitions/observability.py
  • src/synthorg/settings/subscribers/observability_subscriber.py
  • src/synthorg/observability/sink_config_builder.py
  • tests/integration/observability/test_runtime_sink_config_integration.py
  • tests/unit/observability/test_sink_config_builder.py
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.py: No from __future__ import annotations—Python 3.14 has PEP 649
PEP 758 except syntax: use except A, B: (no parentheses)—ruff enforces this on Python 3.14
Type hints: all public functions, mypy strict mode
Docstrings: Google style, required on public classes/functions (enforced by ruff D rules)
Immutability: create new objects, never mutate existing ones. For non-Pydantic internal collections (registries, BaseTool), use copy.deepcopy() at construction + MappingProxyType wrapping for read-only enforcement
Config vs runtime state: frozen Pydantic models for config/identity; separate mutable-via-copy models (using model_copy(update=...)) for runtime state that evolves. Never mix static config fields with mutable runtime fields in one model
Models: Pydantic v2 (BaseModel, model_validator, computed_field, ConfigDict). Adopted conventions: use allow_inf_nan=False in all ConfigDict declarations; use @computed_field for derived values; use NotBlankStr for all identifier/name fields
Async concurrency: prefer asyncio.TaskGroup for fan-out/fan-in parallel operations in new code; prefer structured concurrency over bare create_task
Line length: 88 characters (ruff)
Functions: < 50 lines, files < 800 lines
Errors: handle explicitly, never silently swallow
Validate: at system boundaries (user input, external APIs, config files)

Files:

  • tests/unit/observability/test_sink_routing.py
  • src/synthorg/settings/subscribers/__init__.py
  • src/synthorg/api/app.py
  • src/synthorg/observability/setup.py
  • src/synthorg/observability/events/settings.py
  • src/synthorg/observability/sinks.py
  • tests/unit/settings/test_observability_subscriber.py
  • src/synthorg/settings/definitions/observability.py
  • src/synthorg/settings/subscribers/observability_subscriber.py
  • src/synthorg/observability/sink_config_builder.py
  • tests/integration/observability/test_runtime_sink_config_integration.py
  • tests/unit/observability/test_sink_config_builder.py
tests/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

tests/**/*.py: Markers: @pytest.mark.unit, @pytest.mark.integration, @pytest.mark.e2e, @pytest.mark.slow
Async: asyncio_mode = 'auto'—no manual @pytest.mark.asyncio needed
Timeout: 30 seconds per test (global in pyproject.toml); non-default overrides like timeout(60) are allowed
Parametrize: Prefer @pytest.mark.parametrize for testing similar cases
Property-based testing: Python uses Hypothesis (@given + @settings); Hypothesis profiles: ci (50 examples, default) and dev (1000 examples); run dev profile: HYPOTHESIS_PROFILE=dev uv run python -m pytest tests/ -m unit -n auto -k properties
Flaky tests: NEVER skip, dismiss, or ignore flaky tests—always fix them fully and fundamentally; for timing-sensitive tests, mock time.monotonic() and asyncio.sleep(); for tasks that must block indefinitely, use asyncio.Event().wait()

Files:

  • tests/unit/observability/test_sink_routing.py
  • tests/unit/settings/test_observability_subscriber.py
  • tests/integration/observability/test_runtime_sink_config_integration.py
  • tests/unit/observability/test_sink_config_builder.py
src/synthorg/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

src/synthorg/**/*.py: Every module with business logic MUST have: from synthorg.observability import get_logger then logger = get_logger(name)
Variable name: always logger (not _logger, not log)
Event names: always use constants from domain-specific modules under synthorg.observability.events; import directly from synthorg.observability.events.
Structured kwargs: always logger.info(EVENT, key=value)—never logger.info('msg %s', val)
All error paths must log at WARNING or ERROR with context before raising
All state transitions must log at INFO
DEBUG for object creation, internal flow, entry/exit of key functions
All provider calls go through BaseCompletionProvider which applies retry + rate limiting automatically; never implement retry logic in driver subclasses or calling code

Files:

  • src/synthorg/settings/subscribers/__init__.py
  • src/synthorg/api/app.py
  • src/synthorg/observability/setup.py
  • src/synthorg/observability/events/settings.py
  • src/synthorg/observability/sinks.py
  • src/synthorg/settings/definitions/observability.py
  • src/synthorg/settings/subscribers/observability_subscriber.py
  • src/synthorg/observability/sink_config_builder.py
src/synthorg/**/!(setup|sinks).py

📄 CodeRabbit inference engine (CLAUDE.md)

Never use import logging / logging.getLogger() / print() in application code; exception: observability/setup.py and observability/sinks.py may use stdlib logging and print() for bootstrap code

Files:

  • src/synthorg/settings/subscribers/__init__.py
  • src/synthorg/api/app.py
  • src/synthorg/observability/events/settings.py
  • src/synthorg/settings/definitions/observability.py
  • src/synthorg/settings/subscribers/observability_subscriber.py
  • src/synthorg/observability/sink_config_builder.py
🧠 Learnings (41)
📓 Common learnings
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/settings/**/*.py : Settings package (settings/): runtime-editable settings persistence (DB > env > YAML > code defaults), typed definitions (9 namespaces), Fernet encryption for sensitive values, config bridge (JSON serialization for Pydantic/collections), ConfigResolver (typed accessors), validation, registry, change notifications via message bus, SettingsSubscriber protocol, SettingsChangeDispatcher (polls `#settings` channel, routes to subscribers, restart_required filtering)
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Settings: Runtime-editable settings persistence (DB > env > YAML > code defaults), typed definitions (9 namespaces), Fernet encryption for sensitive values, config bridge, ConfigResolver (typed composed reads for controllers), validation, registry, change notifications via message bus. Per-namespace setting definitions in definitions/ submodule (api, company, providers, memory, budget, security, coordination, observability, backup).
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T06:30:14.180Z
Learning: Applies to src/synthorg/observability/**/*.py : Observability includes structured logging via `get_logger(__name__)`, correlation tracking, and log sinks.
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/observability/**/*.py : Observability package (observability/): structured logging, correlation tracking, log sinks; event constants organized by domain under observability/events/ (e.g., events.api, events.tool, events.git, events.context_budget, events.backup)
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T06:24:56.341Z
Learning: Applies to src/synthorg/observability/**/*.py : Observability must use structured logging with correlation tracking and log sinks
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/observability/**/*.py : Observability package (observability/): structured logging, correlation tracking, log sinks; event constants organized by domain under observability/events/ (e.g., events.api, events.tool, events.git, events.context_budget, events.backup)

Applied to files:

  • docs/api/observability.md
  • src/synthorg/settings/subscribers/__init__.py
  • src/synthorg/api/app.py
  • src/synthorg/observability/setup.py
  • src/synthorg/observability/events/settings.py
  • docs/design/operations.md
  • src/synthorg/observability/sinks.py
  • tests/unit/settings/test_observability_subscriber.py
  • src/synthorg/settings/definitions/observability.py
  • src/synthorg/settings/subscribers/observability_subscriber.py
  • src/synthorg/observability/sink_config_builder.py
  • tests/integration/observability/test_runtime_sink_config_integration.py
  • tests/unit/observability/test_sink_config_builder.py
📚 Learning: 2026-03-17T06:30:14.180Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T06:30:14.180Z
Learning: Applies to src/synthorg/observability/**/*.py : Observability includes structured logging via `get_logger(__name__)`, correlation tracking, and log sinks.

Applied to files:

  • tests/unit/observability/test_sink_routing.py
  • src/synthorg/api/app.py
  • src/synthorg/observability/setup.py
  • docs/design/operations.md
  • src/synthorg/observability/sinks.py
  • tests/unit/settings/test_observability_subscriber.py
  • src/synthorg/settings/definitions/observability.py
  • src/synthorg/settings/subscribers/observability_subscriber.py
  • src/synthorg/observability/sink_config_builder.py
  • tests/integration/observability/test_runtime_sink_config_integration.py
  • tests/unit/observability/test_sink_config_builder.py
📚 Learning: 2026-03-16T06:24:56.341Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T06:24:56.341Z
Learning: Applies to src/synthorg/observability/**/*.py : Observability must use structured logging with correlation tracking and log sinks

Applied to files:

  • tests/unit/observability/test_sink_routing.py
  • src/synthorg/observability/setup.py
  • docs/design/operations.md
  • src/synthorg/observability/sinks.py
  • src/synthorg/settings/definitions/observability.py
  • src/synthorg/settings/subscribers/observability_subscriber.py
  • src/synthorg/observability/sink_config_builder.py
  • tests/integration/observability/test_runtime_sink_config_integration.py
  • tests/unit/observability/test_sink_config_builder.py
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/settings/**/*.py : Settings package (settings/): runtime-editable settings persistence (DB > env > YAML > code defaults), typed definitions (9 namespaces), Fernet encryption for sensitive values, config bridge (JSON serialization for Pydantic/collections), ConfigResolver (typed accessors), validation, registry, change notifications via message bus, SettingsSubscriber protocol, SettingsChangeDispatcher (polls `#settings` channel, routes to subscribers, restart_required filtering)

Applied to files:

  • src/synthorg/settings/subscribers/__init__.py
  • src/synthorg/api/app.py
  • src/synthorg/observability/events/settings.py
  • docs/design/operations.md
  • tests/unit/settings/test_observability_subscriber.py
  • src/synthorg/settings/definitions/observability.py
  • src/synthorg/settings/subscribers/observability_subscriber.py
📚 Learning: 2026-03-30T16:36:12.059Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-30T16:36:12.059Z
Learning: Applies to src/synthorg/**/*.py : Event names: always use constants from domain-specific modules under synthorg.observability.events; import directly from synthorg.observability.events.<domain>

Applied to files:

  • src/synthorg/settings/subscribers/__init__.py
  • src/synthorg/observability/events/settings.py
📚 Learning: 2026-03-18T21:23:23.586Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-18T21:23:23.586Z
Learning: Applies to src/synthorg/**/*.py : Event names: always use constants from the domain-specific module under synthorg.observability.events (e.g., API_REQUEST_STARTED from events.api, TOOL_INVOKE_START from events.tool). Import directly from synthorg.observability.events.<domain>.

Applied to files:

  • src/synthorg/settings/subscribers/__init__.py
  • src/synthorg/observability/events/settings.py
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/communication/**/*.py : Communication package (communication/): message bus, dispatcher, messenger, channels, delegation, loop prevention, conflict resolution; meeting/ subpackage for meeting protocol (round-robin, position papers, structured phases), scheduler (frequency, participant resolver), orchestrator

Applied to files:

  • src/synthorg/api/app.py
📚 Learning: 2026-03-30T16:36:12.059Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-30T16:36:12.059Z
Learning: Applies to src/synthorg/**/!(setup|sinks).py : Never use import logging / logging.getLogger() / print() in application code; exception: observability/setup.py and observability/sinks.py may use stdlib logging and print() for bootstrap code

Applied to files:

  • src/synthorg/observability/setup.py
  • src/synthorg/settings/subscribers/observability_subscriber.py
📚 Learning: 2026-03-17T06:43:14.114Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T06:43:14.114Z
Learning: Applies to src/synthorg/**/*.py : All error paths must log at WARNING or ERROR with context before raising. All state transitions must log at INFO. DEBUG for object creation, internal flow, entry/exit of key functions. Pure data models, enums, and re-exports do NOT need logging.

Applied to files:

  • src/synthorg/observability/setup.py
  • src/synthorg/settings/subscribers/observability_subscriber.py
  • src/synthorg/observability/sink_config_builder.py
📚 Learning: 2026-03-15T19:14:27.144Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T19:14:27.144Z
Learning: Applies to src/synthorg/**/*.py : Use event name constants from synthorg.observability.events domain-specific modules (e.g., PROVIDER_CALL_START from events.provider). Import directly: from synthorg.observability.events.<domain> import EVENT_CONSTANT.

Applied to files:

  • src/synthorg/observability/events/settings.py
📚 Learning: 2026-03-20T11:18:48.128Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T11:18:48.128Z
Learning: Applies to src/synthorg/**/*.py : Use event name constants from domain-specific modules under `synthorg.observability.events` (e.g., `API_REQUEST_STARTED` from `events.api`, `TOOL_INVOKE_START` from `events.tool`). Import directly: `from synthorg.observability.events.<domain> import EVENT_CONSTANT`.

Applied to files:

  • src/synthorg/observability/events/settings.py
📚 Learning: 2026-03-15T18:28:13.207Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:28:13.207Z
Learning: Applies to src/synthorg/**/*.py : Event names: always use constants from domain-specific modules under synthorg.observability.events (e.g., PROVIDER_CALL_START from events.provider, BUDGET_RECORD_ADDED from events.budget, etc.). Import directly: `from synthorg.observability.events.<domain> import EVENT_CONSTANT`.

Applied to files:

  • src/synthorg/observability/events/settings.py
📚 Learning: 2026-03-14T16:18:57.267Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T16:18:57.267Z
Learning: Applies to src/ai_company/!(observability)/**/*.py : Use event name constants from domain-specific modules under `ai_company.observability.events` (e.g., `PROVIDER_CALL_START` from `events.provider`). Import directly: `from ai_company.observability.events.<domain> import EVENT_CONSTANT`.

Applied to files:

  • src/synthorg/observability/events/settings.py
📚 Learning: 2026-03-15T18:38:44.202Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:38:44.202Z
Learning: Applies to src/synthorg/**/*.py : Always use event name constants from domain-specific modules under `synthorg.observability.events` (e.g., `PROVIDER_CALL_START` from `events.provider`); import directly: `from synthorg.observability.events.<domain> import EVENT_CONSTANT`

Applied to files:

  • src/synthorg/observability/events/settings.py
📚 Learning: 2026-03-20T21:44:04.528Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T21:44:04.528Z
Learning: Applies to src/synthorg/**/*.py : Always use event name constants from domain-specific modules under `synthorg.observability.events` (e.g., `API_REQUEST_STARTED` from `events.api`, `TOOL_INVOKE_START` from `events.tool`); import directly rather than using string literals

Applied to files:

  • src/synthorg/observability/events/settings.py
📚 Learning: 2026-03-17T06:30:14.180Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T06:30:14.180Z
Learning: Applies to src/synthorg/settings/**/*.py : Settings use runtime-editable persistence with precedence: DB > env > YAML > code defaults. 8 namespaces with Fernet encryption for sensitive values.

Applied to files:

  • docs/design/operations.md
  • src/synthorg/settings/definitions/observability.py
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Settings: Runtime-editable settings persistence (DB > env > YAML > code defaults), typed definitions (9 namespaces), Fernet encryption for sensitive values, config bridge, ConfigResolver (typed composed reads for controllers), validation, registry, change notifications via message bus. Per-namespace setting definitions in definitions/ submodule (api, company, providers, memory, budget, security, coordination, observability, backup).

Applied to files:

  • docs/design/operations.md
📚 Learning: 2026-03-20T21:44:04.528Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T21:44:04.528Z
Learning: Applies to **/*.py : Use `except A, B:` syntax (without parentheses) per PEP 758 for exception handling in Python 3.14

Applied to files:

  • src/synthorg/settings/subscribers/observability_subscriber.py
📚 Learning: 2026-03-16T07:22:28.134Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T07:22:28.134Z
Learning: Applies to **/*.py : Handle errors explicitly; never silently swallow exceptions

Applied to files:

  • src/synthorg/settings/subscribers/observability_subscriber.py
📚 Learning: 2026-03-16T07:22:28.134Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T07:22:28.134Z
Learning: Applies to **/*.py : Use `except A, B:` syntax (no parentheses) for exception handling — PEP 758 exception syntax enforced by ruff on Python 3.14

Applied to files:

  • src/synthorg/settings/subscribers/observability_subscriber.py
📚 Learning: 2026-03-14T15:43:05.601Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T15:43:05.601Z
Learning: Applies to **/*.py : Handle errors explicitly, never silently swallow exceptions

Applied to files:

  • src/synthorg/settings/subscribers/observability_subscriber.py
📚 Learning: 2026-03-14T16:18:57.267Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T16:18:57.267Z
Learning: Applies to **/*.py : Handle errors explicitly—never silently swallow exceptions.

Applied to files:

  • src/synthorg/settings/subscribers/observability_subscriber.py
📚 Learning: 2026-03-14T16:18:57.267Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T16:18:57.267Z
Learning: Applies to **/*.py : Use PEP 758 except syntax with `except A, B:` (no parentheses) for multiple exceptions—ruff enforces this on Python 3.14.

Applied to files:

  • src/synthorg/settings/subscribers/observability_subscriber.py
📚 Learning: 2026-03-14T15:43:05.601Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T15:43:05.601Z
Learning: Applies to **/*.py : Use PEP 758 except syntax: `except A, B:` (no parentheses) — enforced by ruff on Python 3.14

Applied to files:

  • src/synthorg/settings/subscribers/observability_subscriber.py
📚 Learning: 2026-03-30T16:36:12.059Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-30T16:36:12.059Z
Learning: Applies to **/*.py : PEP 758 except syntax: use `except A, B:` (no parentheses)—ruff enforces this on Python 3.14

Applied to files:

  • src/synthorg/settings/subscribers/observability_subscriber.py
📚 Learning: 2026-03-15T16:55:07.730Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T16:55:07.730Z
Learning: Applies to **/*.py : Use PEP 758 except syntax: use `except A, B:` (no parentheses) — ruff enforces this on Python 3.14.

Applied to files:

  • src/synthorg/settings/subscribers/observability_subscriber.py
📚 Learning: 2026-03-30T16:36:12.059Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-30T16:36:12.059Z
Learning: Applies to **/*.py : Errors: handle explicitly, never silently swallow

Applied to files:

  • src/synthorg/settings/subscribers/observability_subscriber.py
📚 Learning: 2026-03-14T16:18:57.267Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T16:18:57.267Z
Learning: Applies to src/ai_company/!(observability)/**/*.py : All error paths must log at WARNING or ERROR with context before raising.

Applied to files:

  • src/synthorg/settings/subscribers/observability_subscriber.py
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Applies to src/synthorg/**/*.py : All error paths must log at WARNING or ERROR with context before raising. All state transitions must log at INFO. DEBUG for object creation, internal flow, entry/exit of key functions.

Applied to files:

  • src/synthorg/settings/subscribers/observability_subscriber.py
  • src/synthorg/observability/sink_config_builder.py
📚 Learning: 2026-03-16T07:22:28.134Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T07:22:28.134Z
Learning: Applies to src/synthorg/**/*.py : All error paths must log at WARNING or ERROR with context before raising. All state transitions must log at INFO. DEBUG for object creation, internal flow, and key function entry/exit

Applied to files:

  • src/synthorg/settings/subscribers/observability_subscriber.py
📚 Learning: 2026-03-20T11:18:48.128Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T11:18:48.128Z
Learning: Applies to src/synthorg/**/*.py : Every module with business logic MUST have `from synthorg.observability import get_logger` followed by `logger = get_logger(__name__)`.

Applied to files:

  • src/synthorg/observability/sink_config_builder.py
📚 Learning: 2026-03-16T06:24:56.341Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T06:24:56.341Z
Learning: Applies to src/synthorg/**/*.py : Every module with business logic must have: `from synthorg.observability import get_logger` then `logger = get_logger(__name__)`

Applied to files:

  • src/synthorg/observability/sink_config_builder.py
📚 Learning: 2026-03-15T18:28:13.207Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:28:13.207Z
Learning: Applies to src/synthorg/**/*.py : Every module with business logic MUST have: `from synthorg.observability import get_logger` then `logger = get_logger(__name__)`. Never use `import logging` / `logging.getLogger()` / `print()` in application code. Variable name: always `logger` (not `_logger`, not `log`).

Applied to files:

  • src/synthorg/observability/sink_config_builder.py
📚 Learning: 2026-03-19T11:33:01.580Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T11:33:01.580Z
Learning: Applies to src/synthorg/**/*.py : Every module with business logic must import logger via `from synthorg.observability import get_logger` and initialize with `logger = get_logger(__name__)`

Applied to files:

  • src/synthorg/observability/sink_config_builder.py
📚 Learning: 2026-03-15T19:14:27.144Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T19:14:27.144Z
Learning: Applies to src/synthorg/**/*.py : Every module with business logic MUST have: `from synthorg.observability import get_logger` then `logger = get_logger(__name__)`. Never use import logging / logging.getLogger() / print() in application code.

Applied to files:

  • src/synthorg/observability/sink_config_builder.py
📚 Learning: 2026-03-17T06:43:14.114Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T06:43:14.114Z
Learning: Applies to src/synthorg/**/*.py : Every module with business logic MUST have: `from synthorg.observability import get_logger` then `logger = get_logger(__name__)`. Never use `import logging` / `logging.getLogger()` / `print()` in application code. Variable name: always `logger`.

Applied to files:

  • src/synthorg/observability/sink_config_builder.py
📚 Learning: 2026-03-15T18:38:44.202Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:38:44.202Z
Learning: Applies to src/synthorg/**/*.py : Every module with business logic must import `from synthorg.observability import get_logger` and define `logger = get_logger(__name__)`

Applied to files:

  • src/synthorg/observability/sink_config_builder.py
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Applies to src/synthorg/**/*.py : Handle errors explicitly, never silently swallow. Validate at system boundaries (user input, external APIs, config files).

Applied to files:

  • src/synthorg/observability/sink_config_builder.py
📚 Learning: 2026-03-30T16:36:12.059Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-30T16:36:12.059Z
Learning: Applies to **/*.py : Validate: at system boundaries (user input, external APIs, config files)

Applied to files:

  • src/synthorg/observability/sink_config_builder.py
📚 Learning: 2026-03-16T20:14:00.937Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T20:14:00.937Z
Learning: Applies to **/*.py : Validate: at system boundaries (user input, external APIs, config files).

Applied to files:

  • src/synthorg/observability/sink_config_builder.py
🔇 Additional comments (14)
web/public/mockServiceWorker.js (1)

24-27: Good security hardening with early same-origin guard.

This is a solid fix for message-source validation and correctly short-circuits untrusted origins before any client resolution or command handling.

src/synthorg/api/app.py (3)

403-406: No review action needed for this comment-only clarification.


617-621: Bridge initialization refactor is clean and equivalent.

The inline conditional keeps behavior intact while reducing indirection.


726-732: Observability subscriber registration is correctly integrated.

The new subscriber is added with an explicit log_dir fallback and included in dispatcher subscribers.

src/synthorg/settings/definitions/observability.py (1)

35-70: Good addition of runtime sink settings definitions.

The new sink_overrides and custom_sinks registrations are consistent with the observability settings schema and are correctly wired as JSON settings.

docs/api/observability.md (1)

37-39: Docs update is aligned with the new observability surface.

Adding the sink_config_builder API section here is clear and discoverable.

tests/unit/observability/test_sink_routing.py (2)

7-7: Nice move to assert via the public routing table.

Switching tests to SINK_ROUTING improves API-level contract coverage.


70-71: Routing assertions look solid and explicit.

The updated sink/prefix checks are clear and keep the expected routing surface tightly validated.

Also applies to: 74-75, 80-81, 86-87, 115-115, 118-130

src/synthorg/settings/subscribers/__init__.py (1)

9-11: Subscriber re-export is correctly wired.

ObservabilitySettingsSubscriber is properly surfaced through the subscribers package API.

Also applies to: 19-19

src/synthorg/observability/setup.py (2)

183-217: Routing override merge logic is implemented well.

Copy-merge + MappingProxyType keeps default routing immutable while supporting runtime extension.


327-331: configure_logging extension is clean and consistent.

The new routing_overrides parameter is properly documented and passed through to handler attachment.

Also applies to: 344-347, 375-380

src/synthorg/observability/events/settings.py (1)

35-45: New settings-observability event constants look consistent.

Naming and value patterns match the existing event taxonomy in this module.

src/synthorg/observability/sinks.py (2)

57-77: Public SINK_ROUTING export is a good API improvement.

This makes routing expectations explicit and testable without relying on private internals.


207-209: Handler-level routing injection is correctly wired.

build_handler now cleanly supports override routing while preserving the default routing behavior.

Also applies to: 220-223, 227-240

- operations.md: clarify all 4 watched keys trigger pipeline rebuild
- sink_config_builder: require isinstance(str) for routing prefixes
  instead of str() coercion that silently accepts non-strings
- sink_config_builder: reject bool and float for max_bytes/backup_count
  (int(True)==1 and int(3.7)==3 were silently accepted)
- observability_subscriber: replace print() with sys.stderr.write()
  to comply with CLAUDE.md print() ban outside setup.py/sinks.py

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@Aureliolo Aureliolo temporarily deployed to cloudflare-preview March 30, 2026 17:53 — with GitHub Actions Inactive
@Aureliolo Aureliolo merged commit 16c3f23 into main Mar 30, 2026
32 checks passed
@Aureliolo Aureliolo deleted the feat/runtime-sink-config branch March 30, 2026 18:01
@Aureliolo Aureliolo temporarily deployed to cloudflare-preview March 30, 2026 18:01 — with GitHub Actions Inactive
Aureliolo added a commit that referenced this pull request Mar 31, 2026
🤖 I have created a release *beep* *boop*
---


##
[0.5.2](v0.5.1...v0.5.2)
(2026-03-31)


### Features

* harden activity feed API
([#838](#838),
[#839](#839),
[#840](#840))
([#937](#937))
([c0234ad](c0234ad))
* provider usage metrics, model capabilities, and active health probing
([#935](#935))
([1434c9c](1434c9c))
* runtime sink configuration via SettingsService
([#934](#934))
([16c3f23](16c3f23))
* Settings page comprehensive redesign
([#936](#936))
([#939](#939))
([6d9ac8b](6d9ac8b))


### Maintenance

* bump astro from 6.1.1 to 6.1.2 in /site in the all group
([#940](#940))
([ffa24f0](ffa24f0))
* bump pygments from 2.19.2 to 2.20.0
([#931](#931))
([9993088](9993088))
* bump the all group with 2 updates
([#942](#942))
([aea37f8](aea37f8))
* bump typescript-eslint from 8.57.2 to 8.58.0 in /web in the all group
([#941](#941))
([24f024c](24f024c))
* split CLAUDE.md into subdirectory files for cli/ and web/
([#932](#932))
([f5cfe07](f5cfe07))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: runtime sink configuration via SettingsService (add/remove/reconfigure log sinks)

1 participant