Metadata-Version: 2.4
Name: sincpro-framework
Version: 3.5.1
Summary: Sincpro framework to use DDD, Clean architecture, Hexagonal architecture
License: MIT
License-File: LICENSE.md
Author: Gutierrez Andres
Author-email: andru1236@gmail.com
Requires-Python: >=3.12,<4.0
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Provides-Extra: mcp
Provides-Extra: opentelemetry
Provides-Extra: rpc
Provides-Extra: sentry
Requires-Dist: dependency-injector (>=4.48.3,<5.0.0)
Requires-Dist: fastmcp (>=3.0,<4) ; extra == "mcp"
Requires-Dist: opentelemetry-api (>=1.20) ; extra == "opentelemetry"
Requires-Dist: opentelemetry-exporter-otlp-proto-grpc (>=1.20) ; extra == "opentelemetry"
Requires-Dist: opentelemetry-exporter-otlp-proto-http (>=1.20) ; extra == "opentelemetry"
Requires-Dist: opentelemetry-sdk (>=1.20) ; extra == "opentelemetry"
Requires-Dist: pydantic (>=2.12.5,<3.0.0)
Requires-Dist: pyyaml (>=6.0.1)
Requires-Dist: sentry-sdk (>=2.0) ; extra == "sentry"
Requires-Dist: sincpro-log (>=1.2.0,<2.0.0)
Requires-Dist: starlette (>=1.0) ; extra == "rpc"
Requires-Dist: uvicorn (>=0.35) ; extra == "rpc"
Description-Content-Type: text/markdown

# 🚀 Sincpro Framework: Application Layer Framework within Hexagonal Architecture

## ⚡ Quick Start

Here's a quick example to get you started with the Sincpro Framework:

### 🏁 Quick Example

```python
from sincpro_framework import UseFramework, Feature, DataTransferObject

# 1. Initialize the framework
framework = UseFramework("cybersource")

# 2. Add Dependencies (Example dependencies)
from sincpro_framework import Database

db = Database()
framework.add_dependency("db", db)

# 3. Error Handler (Optional)
framework.add_global_error_handler(lambda e: print(f"Error: {e}"))


# 4. Create a Use Case with DTOs
class GreetingParams(DataTransferObject):
    name: str


@framework.feature(GreetingParams)
class GreetingFeature(Feature):
    def execute(self, dto: GreetingParams) -> str:
        self.db.store(f"Greeting {dto.name}")
        return f"Hello, {dto.name}!"


# 5. Execute the Use Case — or export the same catalog as MCP
result = framework(GreetingParams(name="Alice"))
print(result)  # Hello, Alice!

# pip install sincpro-framework[mcp]
from sincpro_framework.entrypoints.mcp import build_mcp_server
build_mcp_server(framework).run()
```

Write the use case once. **`entrypoint_mcp`** publishes the same catalog as MCP tools. **`entrypoint_rpc`** publishes several instances as JSON-RPC methods. No flags on Features — the bus is already the catalog.

Now you are ready to explore more complex use cases! 🚀

## 📑 Table of Contents

1. [Overview of Hexagonal Architecture](#overview-of-hexagonal-architecture)
    - [Key Layers of Hexagonal Architecture](#key-layers-of-hexagonal-architecture)
    - [Why Use a Unified Bus Pattern?](#why-use-a-unified-bus-pattern)
2. [entrypoint_mcp](#entrypoint_mcp)
3. [entrypoint_rpc](#entrypoint_rpc)
4. [Key Features of the Sincpro Framework](#key-features-of-the-sincpro-framework)
    - [DTO Validation with Pydantic](#dto-validation-with-pydantic)
    - [Dependency Injection](#dependency-injection)
    - [Inversion of Control (IoC)](#inversion-of-control-ioc)
    - [Context Manager for Metadata Propagation](#context-manager-for-metadata-propagation)
    - [Middleware System](#middleware-system)
    - [Error Handling at Different Levels](#error-handling-at-different-levels)
    - [Bus Pattern for Component Communication](#bus-pattern-for-component-communication)
    - [Decoupled Logic Execution](#decoupled-logic-execution)
    - [Application Service Orchestration](#application-service-orchestration)
    - [IDE Support with Typing](#ide-support-with-typing)
    - [entrypoint_mcp](#entrypoint_mcp-1)
    - [entrypoint_rpc](#entrypoint_rpc-1)
5. [Features vs. Application Service](#features-vs-application-service)
6. [Example Usage for a Payment Gateway](#example-usage-for-a-payment-gateway)
    - [Configuring the Framework](#configuring-the-framework)
    - [Best Practices for Imports](#best-practices-for-imports)
    - [Sample Configuration in ](#sample-configuration-in-init-py)[`__init__.py`](#sample-configuration-in-init-py)
7. [Recommended Infrastructure Structure](#recommended-infrastructure-structure)
    - [dependencies.py — Adapter Registration](#dependenciespy--adapter-registration)
    - [framework.py — Wiring with DependencyContextType](#frameworkpy--wiring-with-dependencycontexttype)
    - [\_\_init\_\_.py — Bootstrap the Bounded Context](#__init__py--bootstrap-the-bounded-context)
    - [Testing Dependency Consistency](#testing-dependency-consistency)
8. [Creating a Feature](#creating-a-feature)
    - [Example of Creating a Feature](#example-of-creating-a-feature)
9. [Creating an Application Service](#creating-an-application-service)
    - [Example of Creating an Application Service](#example-of-creating-an-application-service)
10. [Executing a Use Case](#executing-a-use-case)
    - [Example of Executing a Use Case](#example-of-executing-a-use-case)
11. [Summary](#summary)
12. [Middleware System](#middleware-system-1)
13. [Observability](#observability) — tracing (OTLP) + errors (Sentry/GlitchTip)
14. [Configuration or settings](#configuration-or-settings)
15. [Variables](#variables)

## 🔍 Overview of Hexagonal Architecture

Hexagonal Architecture, also known as **Ports and Adapters**, is an architectural approach that aims to decouple core
business logic from external dependencies. It organizes the system into distinct layers: domain, application, and
infrastructure, enhancing maintainability, scalability, and adaptability.

### 🏗️ Key Layers of Hexagonal Architecture

- **Core Domain**: This layer encapsulates essential entities, value objects, and domain services representing the core
  business rules and behaviors. It is kept strictly independent from infrastructure concerns, preserving business logic
  integrity.
- **Application Layer**: Orchestrates user requests, processes domain responses, and mediates interactions between
  domain and external systems to ensure effective workflow execution.
- **Infrastructure Layer**: Contains adapters for interacting with databases, APIs, messaging systems, and other
  services. It handles data transformation to ensure compatibility with the domain and application layers.

### 🤔 Why Use a Unified Bus Pattern?

The Sincpro Framework adopts a **unified bus pattern** as a single point of entry for managing use cases, dependencies,
and services within a bounded context. This simplifies the architecture by encapsulating all requirements of a given
context, ensuring a clear and consistent structure.

Using a unified bus allows developers to access all dependencies through a single environment, eliminating the need for
repeated imports or initialization. This approach ensures each bounded context is self-sufficient, independently
scalable, and minimizes coupling while enhancing modularity.

## `entrypoint_mcp`

**Turn the bus into an MCP server. Docstrings contextualize the tools for the LLM.**

A bounded context already *is* an API: DTO in, `execute`, DTO out. **`entrypoint_mcp`** is the MCP host for that catalog — not REST. JSON-RPC is **[`entrypoint_rpc`](#entrypoint_rpc)**. The Feature never learns what FastMCP is. There is no `expose_mcp=True`.

| How you run it | What the client speaks |
|---|---|
| `.run()` | MCP stdio — Cursor, Claude Desktop, CLI |
| `.run(transport="http")` | MCP Streamable HTTP at `/mcp` — still `tools/call`, not REST |

```bash
pip install sincpro-framework[mcp]
```

```python
from sincpro_framework.entrypoints.mcp import build_mcp_server, Entrypoint

# CLI / Cursor / Claude Desktop (stdio)
build_mcp_server(payment_sdk).run()

# Same MCP catalog over the network
build_mcp_server(payment_sdk).run(transport="http", host="127.0.0.1", port=8000)

# Optional: allow-list, then pick the transport
Entrypoint(payment_sdk).include(ChargePayment).exclude(InternalDebug).run()
Entrypoint(payment_sdk).include(ChargePayment).run(transport="http", port=8000)
```

Agents see typed tools generated from your DTOs (Pydantic JSON Schema, Value Object titles, `Field(description=...)`). A `tools/call` is the same operation as `framework(dto)`: validation, middleware, tracing, error handlers.

### Docstrings are the LLM context

The agent **reads** `tools/list` (name + description + field schema) and then **executes** `tools/call`. Nothing else is injected. Write the docstring for that reader — when to use the tool, what not to send, enum values — not Args/Returns (the schema already has types).

```python
class CommandGenerateCUF(DataTransferObject):
    nit: str | int
    """Issuer NIT. Digits only; the Feature zero-pads to 13."""
    modality: SIATModality
    """SIAT modality value, not the Python name. Example: 1 = electrónica."""


@siat_soap_sdk.feature(CommandGenerateCUF)
class GenerateCUF(Feature):
    """Build the SIAT CUF (código único de factura) from NIT, datetime, branch, and modality.

    Use before reception. Does not call SIAT — local encoding only.
    """

    def execute(self, dto: CommandGenerateCUF) -> ResponseGenerateCUF:
        ...
```

| You write | LLM sees |
|---|---|
| Feature class docstring (preferred) | Tool description in `tools/list` |
| else `execute` docstring, else DTO docstring, else DTO name | same |
| Field docstring or `Field(description=...)` | Each argument in the input schema |
| Inherited `Feature` / `ApplicationService` essay | **Ignored** — not published |

A Feature with no own docstring publishes the DTO class name. That is enough to call; it is not enough for an LLM to choose the right tool.

MCP HTTP is still MCP (`tools/call`), not a REST API.

Contract: **[`entrypoint_mcp`](docs/architecture/entrypoint_mcp.md)**. SIAT evaluation: **[use case](docs/architecture/entrypoint_mcp_use_case.md)**.

## `entrypoint_rpc`

**Turn one or more bounded contexts into a JSON-RPC 2.0 server. Methods are `instance.layer.DtoName`.**

Payments mounts `qr`, `cybersource`, and `bank_account` in one process. The prefix exists because DTO names collide (`CommandAuthenticateEconomico` lives in qr and in extractos). Discovery is OpenRPC 1.4 (`rpc.discover` / `GET /openrpc.json`). This is not REST.

```bash
pip install sincpro-framework[rpc]
```

```python
from sincpro_framework.entrypoints.rpc import RpcGateway

RpcGateway({
    "qr": qr,
    "cybersource": cybersource,
    "bank_account": bank_account,
}).run()  # POST http://127.0.0.1:8080/rpc
```

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "qr.features.CommandCreateQREconomico",
  "params": { "transaction_id": "t-1", "amount": 50.0 },
  "context": { "correlation_id": "req-9" }
}
```

`params` are DTO fields. `context` is a JSON-RPC extension (sibling of `params`) and becomes `framework.context` — Features keep using `self.context`. HTTP `X-Correlation-Id` and `traceparent` fill in when the body omits them. `layers=("app_services",)` publishes only ApplicationServices.

Contract: **[`entrypoint_rpc`](docs/architecture/entrypoint_rpc.md)**.

## 🔑 Key Features of the Sincpro Framework

The Sincpro Framework follows hexagonal architecture principles, promoting modularity, scalability, and development
efficiency. Here are its core features:

### ✅ DTO Validation with Pydantic

- Utilizes **Pydantic** to validate Data Transfer Objects (DTOs).
- Ensures only well-structured data is allowed into core business logic, reducing errors and maintaining data integrity.

### 🧩 Dependency Injection

- Facilitates integration of user-defined dependencies, promoting modular design.
- Enhances unit testing by allowing easy mocking or replacement of dependencies.

### 🔄 Inversion of Control (IoC)

- Automates the instantiation and configuration of components, reducing boilerplate code.
- Encourages loose coupling, making systems more adaptable and maintainable.

### � Middleware System

- Allows registering custom functions that run before every Feature or ApplicationService execution.
- Middleware execute **in order**: each one receives the DTO output from the previous step.
- Common uses: validation, authentication checks, data enrichment, and logging.
- Any middleware that raises an exception stops the pipeline immediately.

### �📡 Context Manager for Metadata Propagation

- Provides automatic metadata propagation across Features and ApplicationServices without manual parameter passing.
- Uses Python's `contextvars` for thread-safe context storage and isolation.
- Supports nested contexts with override capabilities for complex workflows.
- Enriches exceptions with context information for better debugging and observability.

```python
# Simple context usage
with app.context({"correlation_id": "123", "user.id": "admin"}) as app_with_context:
    result = app_with_context(some_dto)  # Context automatically available in handlers

# Nested contexts with overrides
with app.context({"env": "prod", "user": "admin"}) as outer_app:
    with outer_app.context({"env": "staging"}) as inner_app:  # Override env, inherit user
        inner_app(dto)  # env="staging", user="admin"

# Access context in Features and ApplicationServices
class PaymentFeature(Feature):
    def execute(self, dto: PaymentDTO) -> PaymentResponse:
        correlation_id = self.context.get("correlation_id")
        user_id = self.context.get("user.id")
        # Use context in business logic...
```

#### Propagating context into a `ThreadPoolExecutor`

- The context overlay lives in a `ContextVar`, which is isolated **per OS thread**. A plain
  `executor.submit(bus.execute, dto)` runs `execute` in a *new* thread that never saw the
  overlay's `set()` — every Feature's `self.context` there silently falls back to the (usually
  empty) shared context.
- `bus.thread_context()` captures the calling thread's current context and returns a
  `ThreadContextBus` — pass `.execute` (not the raw bus) to the executor instead.
- Call `thread_context()` **once per task you submit**, not once for a whole batch: a captured
  `contextvars.Context` can only be entered by one thread at a time, so sharing a single one
  across concurrent workers raises `RuntimeError`.

```python
from concurrent.futures import ThreadPoolExecutor

class SyncManyFeature(ApplicationService):
    def execute(self, dto: SyncManyDTO) -> SyncManyResponse:
        with ThreadPoolExecutor(max_workers=3) as executor:
            futures = [
                # captured here, in this thread, once per task
                executor.submit(self.feature_bus.thread_context().execute, item_dto)
                for item_dto in dto.items
            ]
            results = [f.result() for f in futures]
        return SyncManyResponse(results=results)
```

### ⚠️ Error Handling at Different Levels

- Provides centralized error handling at three independent levels: **global**, **app service**, and **feature**.
- First registered handler executes first. On re-raise, the framework delegates to the next handler in the chain.
- Handlers can be registered **at any point** — before or after the first execution — and take effect immediately.
- Ensures consistent error management, improving overall reliability.

### 🚌 Bus Pattern for Component Communication

- Implements a bus mechanism to facilitate communication between **Feature** and **ApplicationService** components.
- Decouples component interactions, resulting in more flexible and scalable business logic.

### 🧩 Decoupled Logic Execution

- Supports independent execution of use cases through the **Feature** component, promoting separation of concerns.
- For example, a user registration workflow can be broken down into steps like input validation, profile creation, and
  email notification.

### 🎻 Application Service Orchestration

- Uses a **feature bus** to orchestrate multiple features into complex business workflows (e.g., customer onboarding).
- Integrates smaller use cases into cohesive flows to manage entire business processes effectively.

### 💻 IDE Support with Typing

- Uses type hints to enhance code quality and support features like autocompletion and type checking.
- Parameterize the bus as `UseFramework[DependencyContextType]`. Features get `self.token_adapter`; callers outside a Feature get the same instance as `framework.deps.token_adapter`.

### `entrypoint_mcp`

- One line publishes the bus as MCP tools: `build_mcp_server(instance).run()`.
- Features and ApplicationServices become typed tools. Docstrings are the LLM context.
- Domain code stays host-agnostic. This extra is MCP only.

### `entrypoint_rpc`

- One process, several instances: `RpcGateway({"qr": qr, "cybersource": cybersource}).run()`.
- Methods are `qr.features.CommandCreateQREconomico` / `siat.app_services.CommandGenerateCUFD`.
- `context` on the JSON-RPC request is `framework.context` + optional `with_trace`. OpenRPC 1.4 discovery.

### Observability (tracing + errors)

- **Tracing** (optional): OpenTelemetry spans on every DTO, export via OTLP (`sincpro-framework[opentelemetry]` + `OTEL_EXPORTER_OTLP_ENDPOINT`).
- **Errors** (optional): Sentry/GlitchTip capture on bus exceptions (`sincpro-framework[sentry]` + `SENTRY_PYTHON_DSN` in conf). Isolated client — does not call `sentry_sdk.init()`, does not reuse Odoo's client.
- Independent: you can enable traces, errors, both, or neither.

## ⚙️ Features vs. Application Service

- **Feature**: Represents a discrete, self-contained use case focused on specific functionality, easy to develop and
  maintain.
- **ApplicationService**: Orchestrates multiple features for broader business objectives, providing reusable components
  and workflows.

## 💳 Example Usage for a Payment Gateway

The following example shows how to configure the Sincpro Framework for a payment gateway integration, such as
CyberSource. It is recommended to name the framework instance to clearly represent the bounded context it serves.

### 🔧 Configuring the Framework

To set up the Sincpro Framework, configuration should be performed at the application layer within the `use_cases`
directory of each bounded context.

```plaintext
sincpro_payments_sdk/
├── pyproject.toml
├── README.md
├── apps/
│   ├── cybersource/
│   │   ├── adapters/
│   │   │   ├── cybersource_rest_api_adapter.py
│   │   │   └── __init__.py
│   │   ├── domain/
│   │   │   ├── card.py
│   │   │   ├── customer.py
│   │   │   └── __init__.py
│   │   ├── infrastructure/
│   │   │   ├── logger.py
│   │   │   ├── aws_services.py
│   │   │   ├── orm.py
│   │   │   └── __init__.py
│   │   └── use_cases/
│   │       ├── tokenization/
│   │       │   ├── new_tokenization_feature.py
│   │       │   └── __init__.py
│   │       ├── payments/
│   │       │   ├── token_and_payment_service.py
│   │       │   └── __init__.py
│   │       └── __init__.py
│   ├── qr/
│   ├── sms_payment/
│   ├── bank_api/
│   ├── online_payment_gateway/
│   └── paypal_integration/
└── tests
```

### 📋 Best Practices for Imports

Each use case should import both the **DTO for input parameters** and the **DTO for responses** to maintain clarity and
consistency.

### 📝 Sample Configuration in `__init__.py`

```python
from typing import Type

from sincpro_framework import Feature as _Feature
from sincpro_framework import UseFramework as _UseFramework
from sincpro_framework import ApplicationService as _ApplicationService

from sincpro_payments_sdk.apps.cybersource.adapters.cybersource_rest_api_adapter import (
    ESupportedCardType,
    TokenizationAdapter,
)
from sincpro_payments_sdk.infrastructure.orm import with_transaction as db_session
from sincpro_payments_sdk.infrastructure.aws_services import AwsService as aws_service

# Create an instance of the framework
cybersource = _UseFramework()

# Register dependencies
cybersource.add_dependency("token_adapter", TokenizationAdapter())
cybersource.add_dependency("ECardType", ESupportedCardType)
cybersource.add_dependency("db_session", db_session)
cybersource.add_dependency("aws_service", aws_service)


# Define a custom Feature class to access the dependencies
class Feature(_Feature):
    token_adapter: TokenizationAdapter
    ECardType: Type[ESupportedCardType]
    db_session: ...
    aws_service: ...
    logger: ...


# Define a custom Application Service class to access dependencies
class ApplicationService(_ApplicationService):
    token_adapter: TokenizationAdapter
    ECardType: Type[ESupportedCardType]
    db_session: ...
    aws_service: ...
    logger: ...
    feature_bus: ...


# Add use cases (Application Services and Features)
from . import tokenization

__all__ = ["cybersource", "tokenization", "Feature"]
```

## 🏗️ Recommended Infrastructure Structure

When bootstrapping a bounded context with `UseFramework`, the recommended practice is to split
framework wiring into three dedicated files under `apps/<domain>/infrastructure/`:

```plaintext
apps/
└── my_domain/
    ├── infrastructure/
    │   ├── dependencies.py   # registers adapters; declares DependencyContextType
    │   ├── framework.py      # defines local Feature/ApplicationService + config_framework()
    │   └── error_handler.py  # (optional) registers error handlers
    ├── services/
    │   ├── feature_a.py
    │   └── feature_b.py
    └── __init__.py           # creates the framework instance and imports services
```

### `dependencies.py` — Adapter Registration

Declare all external adapters in one place and expose a `DependencyContextType` typing helper.
This class is **not** instantiated — it is used only as a mixin to give `Feature` and
`ApplicationService` subclasses IDE autocomplete for injected attributes.

```python
# apps/my_domain/infrastructure/dependencies.py
from sincpro_framework import UseFramework

from my_sdk.adapters import PaymentAdapter, TokenizationAdapter


class DependencyContextType:
    """Typing helper — gives Features/AppServices IDE autocomplete for injected deps."""

    token_adapter: TokenizationAdapter
    payment_adapter: PaymentAdapter


def register_dependencies(framework: UseFramework[DependencyContextType]) -> UseFramework[DependencyContextType]:
    """Register all adapters with the framework instance."""
    framework.add_dependency("token_adapter", TokenizationAdapter())
    framework.add_dependency("payment_adapter", PaymentAdapter())
    return framework
```

### `framework.py` — Wiring with DependencyContextType

Combine the framework base classes with `DependencyContextType` using multiple inheritance so that
every Feature and ApplicationService in this bounded context automatically inherits the typed
attributes.

```python
# apps/my_domain/infrastructure/framework.py
from sincpro_framework import ApplicationService as _ApplicationService
from sincpro_framework import DataTransferObject  # re-exported for convenience
from sincpro_framework import Feature as _Feature
from sincpro_framework import UseFramework

from .dependencies import DependencyContextType, register_dependencies


class Feature(_Feature, DependencyContextType):
    """Base Feature for this bounded context — typed deps included."""

    pass


class ApplicationService(_ApplicationService, DependencyContextType):
    """Base ApplicationService for this bounded context — typed deps included."""

    pass


def config_framework(name: str) -> UseFramework[DependencyContextType]:
    """Create and configure the framework instance."""
    instance = UseFramework[DependencyContextType](name)
    register_dependencies(instance)
    return instance
```

The same names Features receive as `self.token_adapter` are available on the root as
`my_framework.deps.token_adapter`. Use `self.<name>` inside a Feature / ApplicationService;
use `.deps` from SDK callers, tests, and entrypoints.

### `__init__.py` — Bootstrap the Bounded Context

Create the framework instance first, then import the service modules so that the `@framework.feature`
and `@framework.app_service` decorators register against the already-created instance.

```python
# apps/my_domain/__init__.py
from .infrastructure.framework import (
    ApplicationService,
    DataTransferObject,
    Feature,
    config_framework,
)

my_framework = config_framework("my-domain")

# Import services AFTER creating the instance so decorators register against it
from .services import feature_a, feature_b  # noqa: E402, F401
```

### Testing Dependency Consistency

Assert every name declared on `DependencyContextType` is present on `framework.deps`. If a new
dependency is added to the typing class but forgotten in `register_dependencies`, this test
catches it without registering a Feature.

```python
# tests/my_domain/test_framework_setup.py
from my_sdk.apps.my_domain import my_framework
from my_sdk.apps.my_domain.infrastructure.dependencies import DependencyContextType


def test_declared_deps_are_registered():
    for dep_name in DependencyContextType.__annotations__:
        assert dep_name in my_framework.deps, f"Missing dep: {dep_name}"
```

**Why this matters:**

- Iterates `DependencyContextType.__annotations__` automatically — adding a new dependency to
  the context covers it in the test without any manual edits.
- Catches mismatches between what is declared in `DependencyContextType` and what is actually
  registered via `add_dependency`.

---

## 🛠️ Creating a Feature

To create a new **Feature**, follow these steps:

1. **Create a Module for the Feature**: Add a new Python file in the appropriate folder under `use_cases`.
2. **Import the Framework and Required Classes**: Import the configured framework instance and `DataTransferObject`.
3. **Define the Parameter and Response DTOs**: Use `DataTransferObject` to create classes for input parameters and
   responses.
4. **Create the Feature Class**: Define the `Feature` class by inheriting from the custom `Feature` class.

### 🖋️ Example of Creating a Feature

```python
from sincpro_payments_sdk.apps.cybersource import cybersource, DataTransferObject, Feature


# Define parameter DTO
class TokenizationParams(DataTransferObject):
    card_number: str
    expiration_date: str
    cardholder_name: str


# Define response DTO
class TokenizationResponse(DataTransferObject):
    token: str
    status: str


# Create the Feature class
@cybersource.feature(TokenizationParams)
class NewTokenizationFeature(Feature):
    def execute(self, dto: TokenizationParams) -> TokenizationResponse:
        # Example usage of dependencies
        cybersource.logger.info("Starting tokenization process")
        token = self.token_adapter.create_token(
            card_number=dto.card_number,
            expiration_date=dto.expiration_date,
            cardholder_name=dto.cardholder_name
        )
        return TokenizationResponse(token=token, status="success")
```

## 🔄 Creating an Application Service

**ApplicationService** is used to coordinate multiple features while maintaining reusability and consistency. It
orchestrates features into cohesive workflows.

### 💡 Example of Creating an Application Service

```python
from sincpro_payments_sdk.apps.cybersource import cybersource, DataTransferObject, ApplicationService
from sincpro_payments_sdk.apps.cybersource.use_cases.tokenization import TokenizationParams


# Define parameter DTO
class PaymentServiceParams(DataTransferObject):
    card_number: str
    expiration_date: str
    cardholder_name: str
    amount: float


# Define response DTO
class PaymentServiceResponse(DataTransferObject):
    status: str
    transaction_id: str


# Create the Application Service class
@cybersource.app_service(PaymentServiceParams)
class PaymentOrchestrationService(ApplicationService):
    def execute(self, dto: PaymentServiceParams) -> PaymentServiceResponse:
        # Create the command DTO for tokenization
        tokenization_command = TokenizationParams(
            card_number=dto.card_number,
            expiration_date=dto.expiration_date,
            cardholder_name=dto.cardholder_name
        )
        tokenization_result = self.feature_bus.execute(tokenization_command)

        # Example usage of dependencies
        cybersource.logger.info("Proceeding with payment after tokenization")
        # Proceed with payment using the token (pseudo code for payment processing)
        transaction_id = "12345"  # Simulated transaction ID
        return PaymentServiceResponse(status="success", transaction_id=transaction_id)
```

## ⚙️ Executing a Use Case

Once a **Feature** or **ApplicationService** is defined, it can be executed by passing the appropriate **DTO** instance.

### 📌 Example of Executing a Use Case

```python
from sincpro_payments_sdk.apps.cybersource import cybersource
from sincpro_payments_sdk.apps.cybersource.use_cases.tokenization import TokenizationParams, TokenizationResponse
from sincpro_payments_sdk.apps.cybersource.use_cases.payments import PaymentServiceParams, PaymentServiceResponse

# Example of executing a Feature
feature_dto = TokenizationParams(
    card_number="4111111111111111",
    expiration_date="12/25",
    cardholder_name="John Doe"
)

# Execute the feature
feature_result = cybersource(feature_dto, TokenizationResponse)
print(f"Tokenization Result: {feature_result.token}, Status: {feature_result.status}")

# Example of executing an Application Service
service_dto = PaymentServiceParams(
    card_number="4111111111111111",
    expiration_date="12/25",
    cardholder_name="John Doe",
    amount=100.00
)

# Execute the application service
service_result = cybersource(service_dto, PaymentServiceResponse)
print(f"Payment Status: {service_result.status}, Transaction ID: {service_result.transaction_id}")
```

## 📚 Summary

The Sincpro Framework provides a robust solution for managing the application layer within a hexagonal architecture. By
focusing on decoupling business logic from external dependencies, the framework promotes modularity, scalability, and
maintainability.

- **Features**: Handle specific, self-contained business actions.
- **ApplicationServices**: Orchestrate multiple features for cohesive workflows.
- **`entrypoint_mcp`**: Publish that catalog as MCP tools (`sincpro-framework[mcp]`).
- **`entrypoint_rpc`**: Publish one or more instances as JSON-RPC 2.0 methods (`sincpro-framework[rpc]`).

This structured approach ensures high-quality, maintainable software that can adapt to evolving business needs. 🚀

## � Middleware System

The Sincpro Framework provides a simple and flexible middleware system that lets you add custom processing logic **before** your Features and ApplicationServices are executed.

### Philosophy

The middleware system follows the framework's core principles:
- **Simple**: Middleware is just a function that processes DTOs.
- **Agnostic**: The framework doesn't dictate how you implement middleware.
- **Developer Control**: You have complete control over what your middleware does.

### How It Works

Middleware are plain functions that:
1. Receive a DTO as input.
2. Can validate, transform, or enhance the DTO.
3. Return the (possibly modified) DTO.
4. Can raise exceptions if validation fails.

```python
from typing import Any

def my_middleware(dto: Any) -> Any:
    """Simple middleware that validates or transforms a DTO."""
    if hasattr(dto, 'amount') and dto.amount <= 0:
        raise ValueError("Amount must be positive")
    return dto
```

### Usage

```python
from sincpro_framework import UseFramework

def validate_payment(dto):
    if hasattr(dto, 'amount') and dto.amount <= 0:
        raise ValueError("Amount must be positive")
    return dto

def add_timestamp(dto):
    import time
    if hasattr(dto, '__dict__'):
        dto.timestamp = time.time()
    return dto

framework = UseFramework("my_app")
framework.add_middleware(validate_payment)
framework.add_middleware(add_timestamp)

# All DTOs are processed by middleware before reaching the Feature/Service
result = framework(my_dto)
```

### Execution Order

Middleware execute **in the order they are added**:
1. First middleware processes the original DTO.
2. Second middleware processes the result from the first.
3. And so on…
4. Finally, your Feature or ApplicationService receives the fully processed DTO.

### Common Use Cases

#### Validation
```python
def validate_user_input(dto):
    if hasattr(dto, 'email') and '@' not in dto.email:
        raise ValueError("Invalid email format")
    return dto
```

#### Authentication
```python
def check_authentication(dto):
    if hasattr(dto, 'user_id') and not is_authenticated(dto.user_id):
        raise PermissionError("User not authenticated")
    return dto
```

#### Data Enrichment
```python
def enrich_user_data(dto):
    if hasattr(dto, 'user_id'):
        dto.user_profile = get_user_profile(dto.user_id)
    return dto
```

#### Logging
```python
import logging

def log_requests(dto):
    logging.info(f"Processing DTO: {type(dto).__name__}")
    return dto
```

### Error Handling

If any middleware raises an exception, the entire pipeline stops and the exception propagates to the caller:

```python
def strict_validation(dto):
    if not hasattr(dto, 'required_field'):
        raise ValueError("required_field is missing")
    return dto

framework.add_middleware(strict_validation)
result = framework(my_dto)  # Raises ValueError if required_field is missing
```

### Best Practices

1. **Keep it simple**: Each middleware should do one thing well.
2. **Fail fast**: Raise exceptions early when validation fails.
3. **Be safe**: Always check if attributes exist before accessing them.
4. **Return the DTO**: Always return the DTO (modified or unchanged).
5. **Don't break the chain**: Ensure your middleware doesn't silently swallow exceptions.

## ⚠️ Error Handling

The framework provides three independent error handler scopes: **global** (framework bus), **feature**, and **app service**. Register a handler with the corresponding method — handlers can be added before or after the first execution and always take effect immediately.

### Basic usage

An error handler receives the exception. Return a value to suppress it:

```python
from sincpro_framework import UseFramework

framework = UseFramework("my_app")

def handle_error(error: Exception):
    return {"error": str(error)}  # suppresses the exception

framework.add_global_error_handler(handle_error)
```

### Scoped handlers

Each scope intercepts only the errors produced at that level:

```python
# Only feature errors
framework.add_feature_error_handler(feature_handler)

# Only app service errors
framework.add_app_service_error_handler(app_service_handler)

# Everything that reaches the root bus
framework.add_global_error_handler(global_handler)
```

### Registration lifecycle

Handlers can be registered at any point — before the bus is built or after — and take effect immediately:

```python
framework = UseFramework("my_app")

framework.add_global_error_handler(base_handler)  # before first execution

framework(some_dto)  # first call triggers build

framework.add_global_error_handler(extra_handler)  # after build — also works
```

---

### 🔗 Advanced: Handler chaining

Every call to `add_*_error_handler` adds the handler to a chain. The **first registered handler executes first**. If it re-raises, the framework automatically delegates to the next handler in the chain.

| Registration order | Role | Executes |
|---|---|---|
| `add(h1)` first | Auth — intercepts auth errors early | First |
| `add(h2)` second | Logging — records the error, then delegates | Second |
| `add(h3)` third | Base — produces the structured error response | Last |

#### Example: three-layer chain

```python
# Registration order: auth → observability → base
# Execution order: auth → observability → base

def auth_handler(error: Exception):
    """First — intercepts auth errors; delegates everything else."""
    if isinstance(error, AuthenticationError):
        return {"ok": False, "detail": "unauthenticated", "code": 401}
    raise error  # delegates to observability_handler

def observability_handler(error: Exception):
    """Second — logs error, then delegates to base_handler."""
    log.error("unhandled error", exc_info=error)
    raise error  # delegates to base_handler

def base_handler(error: Exception):
    """Last — always returns a structured error, never raises."""
    return {"ok": False, "detail": str(error)}

framework.add_global_error_handler(auth_handler)          # 1st = runs first
framework.add_global_error_handler(observability_handler) # 2nd
framework.add_global_error_handler(base_handler)          # 3rd = final fallback
```

## �📖 Auto-Documentation

The Sincpro Framework includes a powerful **auto-documentation** feature that automatically generates comprehensive documentation for your framework instances. This documentation includes all your DTOs, Features, Application Services, Dependencies, and Middlewares in multiple formats optimized for different use cases.


### 🚀 Quick Documentation Generation

The easiest way to generate documentation for your project:

```python
from sincpro_framework.generate_documentation import build_documentation

# Import your framework instances from their respective modules
from apps.payment_gateway import payment_framework
from apps.user_management import user_framework

# Generate traditional markdown documentation (default)
build_documentation(
    [payment_framework, user_framework],
    output_dir="docs/generated"
)

# Generate AI-optimized JSON schema
build_documentation(
    [payment_framework, user_framework],
    output_dir="docs/generated",
    format="json"
)

# Generate chunked JSON for optimal AI consumption (NEW!)
build_documentation(
    [payment_framework, user_framework],
    output_dir="docs/generated",
    format="json",
    chunked=True
)

# Generate both formats
build_documentation(
    [payment_framework, user_framework],
    output_dir="docs/generated", 
    format="both"
)
```

### 📋 Output Formats

#### 📝 Markdown Documentation (Traditional)
- **MkDocs-ready**: Complete documentation website with search
- **Human-readable**: Beautiful, professional documentation for developers
- **Interactive**: Searchable content with cross-references

#### 🤖 AI-Optimized JSON Schema (Enhanced!)
- **Complete AI Understanding**: Combines framework context with repository analysis
- **Framework Context**: How to use the Sincpro Framework (patterns, examples, best practices)
- **Repository Analysis**: What components exist in your specific codebase
- **Rich Metadata**: Business domains, complexity analysis, architectural patterns
- **Code Generation**: Comprehensive hints for AI-powered code generation
- **Embedding Support**: Optimized for semantic search and AI embeddings
- **Usage Synthesis**: Real examples combining framework knowledge with repository components

#### 🔥 NEW: Chunked JSON for AI Token Optimization
- **Progressive Discovery**: AI can understand what exists without loading all details
- **Massive Size Reduction**: Up to 96.7% smaller for multiple framework instances
- **Token Efficiency**: Shared framework context across all instances (~70KB once)
- **Selective Loading**: Load only needed chunks (DTOs, Features, Services)
- **Smart Categorization**: Automatic business domain inference and complexity analysis

### 📁 Generated Documentation Structure

#### Traditional Structure
```
docs/generated/
├── mkdocs.yml                    # MkDocs configuration
├── requirements.txt              # Dependencies
├── framework_schema.json         # AI-optimized JSON with framework context
├── site/                        # Built HTML documentation
└── docs/                        # Markdown content
    ├── index.md                 # Overview
    ├── features.md              # Features documentation
    ├── dtos.md                  # DTOs documentation
    └── application-services.md  # Services documentation
```

#### NEW: Chunked Structure (AI-Optimized)
```
docs/generated/ai_context/
├── 01_framework_context.json           # Shared framework knowledge (70KB)
├── 01_payment_gateway_context.json     # Instance overview (1-2KB)
├── 01_payment_gateway_dtos.json        # DTO summaries (700B)
├── 01_payment_gateway_dtos_details.json # Full DTO details (1-3KB)
├── 01_payment_gateway_features.json    # Feature summaries (700B)
├── 01_payment_gateway_features_details.json # Full feature details (1-3KB)
├── 01_payment_gateway_services.json    # Service summaries (if any)
├── 01_payment_gateway_services_details.json # Full service details
├── 02_user_management_context.json     # Second instance overview
├── 02_user_management_dtos.json        # Second instance DTOs
└── ...                                 # Additional instances
```

### 🤖 AI-Optimized JSON Schema Features

The enhanced JSON schema combines framework context with repository analysis for complete AI understanding:

```json
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "title": "Repository Schema with Framework Context",
  "schema_type": "ai_optimized_complete",
  
  "framework_context": {
    "framework_name": "Sincpro Framework",
    "core_principles": {/* Framework usage patterns and principles */},
    "key_features": {/* Framework capabilities and features */},
    "framework_execution_patterns": {/* How to execute features/services */}
  },
  
  "repository_analysis": {
    "metadata": {
      "architecture_patterns": ["DDD", "Clean Architecture"],
      "component_summary": { /* counts and statistics */ }
    },
    "components": {
      "dtos": [/* with AI hints for type classification */],
      "features": [/* with business domain inference */],
      "application_services": [/* with orchestration patterns */]
    }
  },
  
  "ai_integration": {
    "framework_integration": {
      "execution_patterns": {/* How to use framework with repository components */},
      "available_features": {/* Framework capabilities */}
    },
    "complete_understanding": {
      "framework_knowledge": "Loaded from hardcoded guide",
      "repository_knowledge": "Generated from code analysis",
      "ai_capability": "Complete understanding of framework usage + repository components"
    },
    "usage_synthesis": {
      "how_to_execute_features": {/* Real examples combining framework + repo */},
      "how_to_execute_services": {/* Real examples combining framework + repo */}
    },
    "embedding_suggestions": {
      "primary_entities": ["PaymentCommand", "UserCommand"],
      "business_capabilities": ["PaymentFeature", "UserFeature"]
    },
    "code_generation_hints": {
      "framework_patterns": ["command_pattern", "dependency_injection"],
      "common_imports": ["from sincpro_framework import..."]
    },
    "complexity_analysis": {
      "overall_complexity": "medium",
      "most_complex_components": ["ComplexService"]
    }
  }
}
```

### 🎯 Chunked JSON Benefits for AI Consumption

The new chunked approach provides significant advantages for AI systems:

#### 📊 Size Reduction Examples
- **Single Instance**: Traditional 90KB → Chunked 10KB (89% reduction)
- **Two Instances**: Traditional 180KB → Chunked 80KB (56% reduction) 
- **Five Instances**: Traditional 450KB → Chunked 110KB (76% reduction)
- **Twenty Instances**: Traditional 1.8MB → Chunked 250KB (86% reduction)

#### 🧠 Progressive AI Discovery Pattern
1. **Start with Framework Context** (`01_framework_context.json` - 70KB)
   - Learn how to use Sincpro Framework
   - Understand patterns and principles
   - Get execution examples

2. **Instance Overview** (`01_<name>_context.json` - 1-2KB each)
   - Quickly understand what components exist
   - See component counts and names
   - Identify available detail files

3. **Component Summaries** (`01_<name>_dtos.json` - 700B each)
   - Get basic component information
   - Understand business domains
   - Assess complexity levels

4. **Detailed Information** (`01_<name>_dtos_details.json` - 1-3KB each)
   - Load full component details when needed
   - Complete field information
   - Implementation details

#### 🤖 AI Token Optimization
- **Traditional**: Load everything at once (high token cost)
- **Chunked**: Load progressively as needed (optimized token usage)
- **Reusability**: Framework context shared across all instances
- **Selectivity**: Load only relevant component types (DTOs, Features, Services)

### ✨ Documentation Features

#### Traditional Markdown
- **🎨 Sincpro Theme**: Beautiful violet corporate colors and professional styling
- **📱 Responsive Design**: Works perfectly on desktop and mobile devices
- **🔍 Full-Text Search**: Find any component, method, or parameter instantly
- **📊 Component Overview**: Summary tables with component counts and descriptions

#### AI-Optimized JSON
- **🧠 Business Domain Inference**: Automatic categorization (payments, users, orders)
- **📈 Complexity Assessment**: Automatic complexity analysis for optimization
- **🔍 Pattern Recognition**: Identification of architectural patterns
- **🤖 AI Hints**: Rich metadata for AI understanding and code generation

### 🎯 Best Practices

1. **Document your DTOs**: Add clear docstrings to your Data Transfer Objects
2. **Describe your Features**: Include comprehensive docstrings for execute methods
3. **Name components clearly**: Use descriptive names for better auto-generated docs
4. **Organize by domain**: Group related features and services logically

Example of well-documented code:

```python
class PaymentCommand(DataTransferObject):
    """Command for processing credit card payments.

    This DTO contains all necessary information to process
    a payment transaction through the payment gateway.
    """
    card_number: str  # Credit card number (PCI compliant)
    amount: float     # Payment amount in USD
    merchant_id: str  # Unique merchant identifier

@framework.feature(PaymentCommand)
class PaymentFeature(Feature):
    """Process payment transactions through external gateway.

    This feature handles the complete payment flow including
    validation, gateway communication, and response processing.
    """

    def execute(self, dto: PaymentCommand) -> PaymentResponse:
        """Execute payment processing.

        Args:
            dto: Payment command with card and amount information

        Returns:
            PaymentResponse: Transaction result with ID and status

        Raises:
            PaymentError: When payment fails or card is invalid
        """
        # Implementation here...
```

### 🚀 Integration with AI Systems

The JSON schema format enables powerful AI integrations:

- **Code Generation**: AI can understand patterns and generate similar code
- **Documentation**: AI can explain components and their relationships  
- **Analysis**: AI can identify optimization opportunities and suggest improvements
- **Migration**: AI can understand dependencies for migration planning

## Observability

The bus always instruments. Extras and env vars only decide **where** data goes.

| Signal | Extra | Env | Backend |
|---|---|---|---|
| Logs (`trace_id` / `span_id`) | none | — | stdout / your logger |
| Tracing (spans) | `[opentelemetry]` | `OTEL_EXPORTER_OTLP_ENDPOINT` | Tempo / Jaeger |
| Errors (exceptions) | `[sentry]` | `SENTRY_PYTHON_DSN` (framework conf) | GlitchTip / Sentry |

Missing extra or missing DSN in conf → no-op, the bus still raises. Framework events are independent from the host: Odoo may also capture the same exception with its own release. That is intended.

### What works without any extra install

Every `framework(dto)` call automatically tags all internal log lines with a `trace_id` and `span_id`. These are UUID-based identifiers — enough to correlate all logs produced by a single execution even without an external tracing backend.

You can also read them inside any Feature or ApplicationService:

```python
class MyFeature(Feature):
    def execute(self, dto: MyDTO) -> MyResponse:
        trace_id = self.context.get("trace_id")  # always present
        ...
```

### Installing with OpenTelemetry

```bash
pip install sincpro-framework[opentelemetry]
```

This installs:
- `opentelemetry-api` + `opentelemetry-sdk`
- `opentelemetry-exporter-otlp-proto-grpc` (primary)
- `opentelemetry-exporter-otlp-proto-http` (fallback)

### Sentry / GlitchTip (errors)

Same silent contract as OTel. The bus **always** tries to report exceptions; if `sentry-sdk` is missing or the DSN in conf is unset, it is a no-op.

```bash
pip install sincpro-framework[sentry]
export SENTRY_PYTHON_DSN=https://KEY@glitchtip.sincpro.dev/1
```

Conf (`sincpro_framework/conf/sincpro_framework_conf.yml`) resolves `sentry_dsn` from `SENTRY_PYTHON_DSN`. If that env is set, `observability_status()["sentry"]` is `on:init`, not `off`. The framework does **not** call `sentry_sdk.init()` and does not reuse the host client.

Each framework event uses an isolated Sentry `Client` whose `release` is computed once at `UseFramework` init: `{app_name}:{library_version}` (Poetry/installed dist), not the Python version. Example: `payment-cybersource:5.0.3`. Framework-internal errors use `sincpro-framework:<framework version>`.

When the caller isn't an installed distribution (e.g. a service entrypoint, not a library) `library_version` can't be resolved and falls back to the `APP_RELEASE` env var, so services still get a real release instead of `payment-cybersource:unknown`.

GlitchTip `environment` is `TENANT` (same value as the `tenant` tag) so events can be filtered by tenant in the UI.

Odoo may capture the same exception with Odoo's release. That second event is intended — two products, two releases, same traceback.

The bus reports **before** the error handler runs. A handler that swallows an unexpected exception still produces a GlitchTip event. Expected domain errors can be excluded per instance:

```python
app = UseFramework("payment-cybersource")  # release auto-detected from the caller package
app.ignore_sentry_exceptions(ValidationError, InsufficientFunds)
```

Pass `package="sincpro-payments-sdk"` to `UseFramework` when the caller is not the library itself (tests, a thin adapter).

Observability is optional and must never break the bus. After `build_root_bus()` (or the first `app(dto)` call) every instance exposes a probe:

```python
status = app.observability_status()
status["sentry"]  # {active, state, reason}  state: off | on | failed
status["otel"]
```

- `off` — extra not installed or conf DSN missing (`sdk_missing`, `dsn_missing`)
- `on` — isolated client ready (`init`)
- `failed` — DSN present but client construction broke; the bus still runs. Logged as **warning**.

The instance logger emits: `observability sentry=on:init otel=off:no_endpoint`.

Do not send traces to GlitchTip (`traces_sample_rate=0`); Tempo stays on OTLP.

### What OpenTelemetry adds

| Without OTel | With OTel |
|---|---|
| UUID-based trace/span IDs in logs | Real OTel spans with proper trace IDs |
| No span hierarchy | `ApplicationService` span wraps `Feature` spans |
| No OTLP export | Exports to Jaeger, Grafana Tempo, Honeycomb, etc. |
| No W3C traceparent propagation | Parent context from HTTP headers via `carrier=` |
| No external span adoption | Auto-adopts active span from FastAPI, Celery, etc. |

When OTel is installed, auto-adoption of outer spans happens transparently — any active span already in the OTel context (set by FastAPI OpenTelemetry middleware, a Celery task decorator, etc.) becomes the parent of all sincpro spans without any extra setup.

### Configuring the OTLP exporter

Set the endpoint in your environment or the framework config file:

```bash
export OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317
```

Or in `sincpro_framework/conf/sincpro_framework_conf.yml`:

```yaml
otlp_endpoint: $ENV:OTEL_EXPORTER_OTLP_ENDPOINT
```

Register the provider once at startup, before any `framework(dto)` call:

```python
from sincpro_framework.tracing import setup_otlp_provider

setup_otlp_provider("payments-service")
```

The provider is a **process-level singleton** — only the first call registers it. Subsequent calls (e.g. from a second bounded context) are no-ops.

### The `with_trace()` context manager

Use `with_trace()` when you need explicit control over the trace boundary — for example, to group multiple `framework(dto)` calls under a single trace, or to accept a trace from an upstream caller.

**Fresh trace** (useful in CLI scripts, workers, or test harnesses):

```python
with framework.with_trace() as fw:
    result = fw(MyDTO(...), MyResponse)
    # all logs inside this block share the same trace_id/span_id
```

**Propagate from upstream HTTP headers** (W3C `traceparent`):

```python
# headers = {"traceparent": "00-<trace_id>-<parent_id>-01", ...}
with framework.with_trace(carrier=request.headers) as fw:
    result = fw(ProcessOrderDTO(...), OrderResult)
```

**Explicit IDs** (re-use IDs from a previous system that doesn't speak W3C):

```python
with framework.with_trace(trace_id="abc123", span_id="def456") as fw:
    result = fw(MyDTO(...), MyResponse)
```

### Span attributes

Every span produced by the framework carries:

| Attribute | Value | Purpose |
|---|---|---|
| `sincpro.layer` | `"feature"` or `"application_service"` | Identify which bus layer handled the DTO |
| `sincpro.instance` | The framework instance name (e.g. `"payments"`) | Distinguish bounded contexts — useful when multiple `UseFramework` instances share one process, since the OTLP `service.name` Resource reflects only the first registered instance |

### Embedding sincpro inside another instrumented service (Odoo, FastAPI, etc.)

`setup_otlp_provider` always creates a **private** TracerProvider for sincpro with its own `service.name`. If the host application (Odoo, FastAPI, Celery) already registered the global OTel provider, sincpro leaves it untouched and keeps its provider internal.

The trace relationship is still preserved: OTel propagates the active parent span via `contextvars` (process-wide), so sincpro spans are automatically children of whatever span the host has active at call time. In Tempo/Jaeger the full tree is visible and filterable:

```
service.name=odoo          →  GET /web/dataset/call_kw     (Odoo HTTP span)
service.name=my-service    →    └── CreateOrderDTO          (application_service)
service.name=my-service    →         └── ValidateStockDTO   (feature)
```

Sampling is respected across the boundary: sincpro uses `ParentBased(root=ALWAYS_ON)`, so when the host did not sample a trace, sincpro's spans are also dropped. When running standalone (no host parent span), every span is sampled.

## Configuration or settings

The framework comes with a module or component to allow us to create configuratio or settings based on files or
environment variables.
You need to inherit from `SincproConfig` from module `sincpro_framework.sincpro_conf`

```python
from sincpro_framework.sincpro_conf import SincproConfig


class PostgresConf(SincproConfig):
    host: str = "localhost"
    port: int = 5432
    user: str = "my_user"


class MyConfig(SincproConfig):
    log_level: str = "DEBUG"
    token: str = "defult_my_token"
    postgresql: PostgresConf = PostgresConf()

```

This class should be mapped based on yaml file like this, we have a feature to use ENV variables in the yaml file
using the prefix `$ENV:`

```yaml
log_level: "INFO"
token: "$ENV:MY_SECRET_TOKEN"
postgresql:
  host: localhost
  port: 12345
  user: custom_user
```

### Environment Variable Handling

When using `$ENV:` prefix in your configuration files, the framework will:

1. Look for the environment variable specified after `$ENV:`
2. If the environment variable exists, use its value
3. If the environment variable doesn't exist:
   - Use the default value defined in your configuration class
   - Issue a warning indicating that the environment variable is missing
   - Proceed with execution rather than raising an error

This behavior allows applications to run with partial configurations in development environments or when not all environment variables are available, while still logging the fallback at info level.

Example of fallback to default values:

```python
# Configuration class with default
class ApiConfig(SincproConfig):
    api_key: str = "dev_default_key"  # Default value as fallback

# In config.yml
api_key: "$ENV:API_KEY"  # References environment variable

# If API_KEY environment variable is not set, the framework will:
# 1. Log info: "Environment variable [API_KEY] is not set for field [api_key]. Using default value: dev_default_key"
# 2. Use the default value "dev_default_key"
# 3. Continue execution without error
```

Then you can use the config object in your code where it will be loaded all the settings from the yaml file
for that you will require use the following funciton `build_config_obj`

```python
from sincpro_framework.sincpro_conf import build_config_obj
from .my_config import MyConfig

config = build_config_obj(MyConfig, '/path/to/your/config.yml')

assert isinstance(config.log_level, str)
assert isinstance(config.postgresql, PostgresConf)
```

## 📦 Variables

The framework use a default setting file where live in the module folder inside of
`sincpro_framework/conf/sincpro_framework_conf.yml`
where you can define some behavior currently we support the following settings:

- `sincpro_framework_log_level`: Log level for the framework logger. Default: `DEBUG`.
- `otlp_endpoint`: OTLP exporter endpoint for distributed tracing. Resolved from `OTEL_EXPORTER_OTLP_ENDPOINT` env var. Default: `null` (tracing disabled). Requires `sincpro-framework[opentelemetry]`.
- `sentry_dsn`: GlitchTip/Sentry DSN. Resolved from `SENTRY_PYTHON_DSN`. Default: `null` (error reporting disabled). Requires `sentry-sdk` (or `sincpro-framework[sentry]`). The framework uses an isolated client with `release={app_name}:{library_version}` and never calls `sentry_sdk.init()`. Odoo may capture the same error separately. Use `UseFramework.ignore_sentry_exceptions(...)` for expected errors.

Override the config file using another

```bash
export SINCPRO_FRAMEWORK_CONFIG_FILE = /path/to/your/config.yml
```

