# Story 10.23: Builder Core Settings Methods

**Status:** Complete
**Priority:** High
**Depends on:** Story 10.22 (Builder API Parity Foundation)

---

## Context / Background

With the foundation established in Story 10.22, this story implements builder methods for all `CoreSettings` fields that lack coverage. These are the most commonly needed configuration options for production deployments.

**Current CoreSettings fields WITHOUT builder methods:**

| Field | Description | Priority |
|-------|-------------|----------|
| `backpressure_wait_ms` | Queue backpressure timing | P0 |
| `drop_on_full` | Backpressure behavior | P0 |
| `sink_circuit_breaker_enabled` | Fault isolation | P0 |
| `sink_circuit_breaker_failure_threshold` | Circuit breaker config | P0 |
| `sink_circuit_breaker_recovery_timeout_seconds` | Circuit breaker config | P0 |
| `worker_count` | Parallelism | P1 |
| `shutdown_timeout_seconds` | Graceful shutdown | P1 |
| `exceptions_enabled` | Exception handling | P1 |
| `exceptions_max_frames` | Exception config | P1 |
| `exceptions_max_stack_chars` | Exception config | P1 |
| `enable_metrics` | Observability | P1 |
| `sink_parallel_writes` | Performance | P1 |
| `strict_envelope_mode` | Reliability | P2 |
| `serialize_in_flush` | Performance | P2 |
| `error_dedupe_window_seconds` | Noise reduction | P2 |
| `internal_logging_enabled` | Debugging | P2 |
| `diagnostics_output` | Debugging | P2 |
| `app_name` | Identification | P2 |
| `context_binding_enabled` | Feature toggle | P2 |
| `capture_unhandled_enabled` | Exception hooks | P2 |
| `resource_pool_max_size` | Resource management | P2 |
| `resource_pool_acquire_timeout_seconds` | Resource management | P2 |
| `fallback_redact_mode` | Security | P2 |
| `sensitive_fields_policy` | Policy | P2 |
| `enable_redactors` | Feature toggle | P2 |
| `redactors_order` | Configuration | P2 |

---

## Scope (In / Out)

### In Scope

- Builder methods for all P0 and P1 CoreSettings fields
- Builder methods for P2 fields where trivial to implement
- Unit tests for each new method
- Docstrings with examples

### Out of Scope

- Sink-specific builders (Story 10.24)
- Filter/processor builders (Story 10.25)
- Sink routing, advanced redactor config (Story 10.26)
- Documentation beyond docstrings (Story 10.28)

---

## Acceptance Criteria

### AC1: Circuit Breaker Configuration

**Description:** Users can configure sink circuit breakers via builder.

**Validation:**
```python
logger = (
    LoggerBuilder()
    .with_circuit_breaker(
        enabled=True,
        failure_threshold=5,
        recovery_timeout="30s",
    )
    .add_http("https://logs.example.com")
    .build()
)

# Equivalent to:
settings = Settings(
    core=CoreSettings(
        sink_circuit_breaker_enabled=True,
        sink_circuit_breaker_failure_threshold=5,
        sink_circuit_breaker_recovery_timeout_seconds=30.0,
    )
)
```

### AC2: Backpressure Configuration

**Description:** Users can configure queue backpressure via builder.

**Validation:**
```python
logger = (
    LoggerBuilder()
    .with_backpressure(wait_ms=100, drop_on_full=False)
    .build()
)

# Equivalent to:
settings = Settings(
    core=CoreSettings(
        backpressure_wait_ms=100,
        drop_on_full=False,
    )
)
```

### AC3: Worker Configuration

**Description:** Users can configure worker count and shutdown behavior.

**Validation:**
```python
logger = (
    LoggerBuilder()
    .with_workers(count=4)
    .with_shutdown_timeout("5s")
    .build()
)
```

### AC4: Exception Handling Configuration

**Description:** Users can configure exception serialization.

**Validation:**
```python
logger = (
    LoggerBuilder()
    .with_exceptions(
        enabled=True,
        max_frames=20,
        max_stack_chars=50000,
    )
    .build()
)
```

### AC5: Performance Configuration

**Description:** Users can configure performance-related settings.

**Validation:**
```python
logger = (
    LoggerBuilder()
    .with_parallel_sink_writes(enabled=True)
    .with_metrics(enabled=True)
    .build()
)
```

### AC6: All Methods Chainable

**Description:** All new methods return `Self` for fluent chaining.

**Validation:**
```python
logger = (
    LoggerBuilder()
    .with_level("INFO")
    .with_circuit_breaker(enabled=True)
    .with_backpressure(wait_ms=50)
    .with_workers(count=2)
    .with_exceptions(max_frames=15)
    .with_parallel_sink_writes(enabled=True)
    .add_stdout()
    .build()
)
```

### AC7: Parity Test Passes for Core Settings

**Description:** The parity test from Story 10.22 passes for CoreSettings.

**Validation:**
```bash
pytest tests/test_builder_parity.py::test_all_core_settings_have_builder_coverage -v
# Should pass (or have only intentional exclusions)
```

---

## Implementation Notes

### File Changes

```
src/fapilog/builder.py (MODIFIED - add ~15 new methods)
tests/unit/test_builder_core_settings.py (NEW)
tests/test_builder_parity.py (MODIFIED - update coverage mapping)
```

### New Builder Methods

```python
# src/fapilog/builder.py additions

def with_circuit_breaker(
    self,
    *,
    enabled: bool = True,
    failure_threshold: int = 5,
    recovery_timeout: str | float = "30s",
) -> LoggerBuilder:
    """Configure sink circuit breaker for fault isolation.

    Args:
        enabled: Enable circuit breaker (default: True)
        failure_threshold: Consecutive failures before opening circuit
        recovery_timeout: Time before probing failed sink ("30s" or 30.0)

    Example:
        >>> builder.with_circuit_breaker(enabled=True, failure_threshold=3)
    """
    core = self._config.setdefault("core", {})
    core["sink_circuit_breaker_enabled"] = enabled
    core["sink_circuit_breaker_failure_threshold"] = failure_threshold
    core["sink_circuit_breaker_recovery_timeout_seconds"] = self._parse_duration(recovery_timeout)
    return self

def with_backpressure(
    self,
    *,
    wait_ms: int = 50,
    drop_on_full: bool = True,
) -> LoggerBuilder:
    """Configure queue backpressure behavior.

    Args:
        wait_ms: Milliseconds to wait for queue space (default: 50)
        drop_on_full: Drop events if queue still full after wait (default: True)

    Example:
        >>> builder.with_backpressure(wait_ms=100, drop_on_full=False)
    """
    core = self._config.setdefault("core", {})
    core["backpressure_wait_ms"] = wait_ms
    core["drop_on_full"] = drop_on_full
    return self

def with_workers(self, count: int = 1) -> LoggerBuilder:
    """Set number of worker tasks for flush processing.

    Args:
        count: Number of workers (default: 1)

    Example:
        >>> builder.with_workers(count=4)
    """
    self._config.setdefault("core", {})["worker_count"] = count
    return self

def with_shutdown_timeout(self, timeout: str | float = "3s") -> LoggerBuilder:
    """Set maximum time to flush on shutdown.

    Args:
        timeout: Shutdown timeout ("3s" or 3.0)

    Example:
        >>> builder.with_shutdown_timeout("5s")
    """
    self._config.setdefault("core", {})["shutdown_timeout_seconds"] = self._parse_duration(timeout)
    return self

def with_exceptions(
    self,
    *,
    enabled: bool = True,
    max_frames: int = 10,
    max_stack_chars: int = 20000,
) -> LoggerBuilder:
    """Configure exception serialization.

    Args:
        enabled: Enable structured exception capture (default: True)
        max_frames: Maximum stack frames to capture (default: 10)
        max_stack_chars: Maximum total stack string length (default: 20000)

    Example:
        >>> builder.with_exceptions(max_frames=20)
    """
    core = self._config.setdefault("core", {})
    core["exceptions_enabled"] = enabled
    core["exceptions_max_frames"] = max_frames
    core["exceptions_max_stack_chars"] = max_stack_chars
    return self

def with_parallel_sink_writes(self, enabled: bool = True) -> LoggerBuilder:
    """Enable parallel writes to multiple sinks.

    Args:
        enabled: Write to sinks in parallel (default: True)

    Example:
        >>> builder.with_parallel_sink_writes(enabled=True)
    """
    self._config.setdefault("core", {})["sink_parallel_writes"] = enabled
    return self

def with_metrics(self, enabled: bool = True) -> LoggerBuilder:
    """Enable Prometheus-compatible metrics.

    Args:
        enabled: Enable metrics collection (default: True)

    Example:
        >>> builder.with_metrics(enabled=True)
    """
    self._config.setdefault("core", {})["enable_metrics"] = enabled
    return self

def with_error_deduplication(self, window_seconds: float = 5.0) -> LoggerBuilder:
    """Configure error log deduplication.

    Args:
        window_seconds: Seconds to suppress duplicate errors (0 disables)

    Example:
        >>> builder.with_error_deduplication(window_seconds=10.0)
    """
    self._config.setdefault("core", {})["error_dedupe_window_seconds"] = window_seconds
    return self

def with_diagnostics(
    self,
    *,
    enabled: bool = True,
    output: Literal["stderr", "stdout"] = "stderr",
) -> LoggerBuilder:
    """Configure internal diagnostics output.

    Args:
        enabled: Enable internal logging (default: True)
        output: Output stream ("stderr" or "stdout")

    Example:
        >>> builder.with_diagnostics(enabled=True, output="stderr")
    """
    core = self._config.setdefault("core", {})
    core["internal_logging_enabled"] = enabled
    core["diagnostics_output"] = output
    return self

def with_app_name(self, name: str) -> LoggerBuilder:
    """Set application name for log identification.

    Args:
        name: Application name

    Example:
        >>> builder.with_app_name("my-service")
    """
    self._config.setdefault("core", {})["app_name"] = name
    return self

def with_strict_mode(self, enabled: bool = True) -> LoggerBuilder:
    """Enable strict envelope mode (drop on serialization failure).

    Args:
        enabled: Enable strict mode (default: True)

    Example:
        >>> builder.with_strict_mode(enabled=True)
    """
    self._config.setdefault("core", {})["strict_envelope_mode"] = enabled
    return self

def with_unhandled_exception_capture(self, enabled: bool = True) -> LoggerBuilder:
    """Enable automatic capture of unhandled exceptions.

    Args:
        enabled: Install exception hooks (default: True)

    Example:
        >>> builder.with_unhandled_exception_capture(enabled=True)
    """
    self._config.setdefault("core", {})["capture_unhandled_enabled"] = enabled
    return self
```

---

## Tasks

### Phase 1: P0 Methods (Circuit Breaker, Backpressure)

- [ ] Implement `with_circuit_breaker()`
- [ ] Implement `with_backpressure()`
- [ ] Add unit tests for circuit breaker method
- [ ] Add unit tests for backpressure method
- [ ] Reuse `_parse_duration()` from `fapilog.core.types` (already exists in codebase)

### Phase 2: P1 Methods (Workers, Exceptions, Performance)

- [ ] Implement `with_workers()`
- [ ] Implement `with_shutdown_timeout()`
- [ ] Implement `with_exceptions()`
- [ ] Implement `with_parallel_sink_writes()`
- [ ] Implement `with_metrics()`
- [ ] Add unit tests for each method

### Phase 3: P2 Methods (Diagnostics, Misc)

- [ ] Implement `with_error_deduplication()`
- [ ] Implement `with_diagnostics()`
- [ ] Implement `with_app_name()`
- [ ] Implement `with_strict_mode()`
- [ ] Implement `with_unhandled_exception_capture()`
- [ ] Add unit tests for each method

### Phase 4: Integration

- [ ] Update parity test coverage mapping
- [ ] Verify all methods chain correctly
- [ ] Verify Settings equivalence for each method
- [ ] Run full test suite

---

## Tests

### Unit Tests

- `tests/unit/test_builder_core_settings.py`
  - test_with_circuit_breaker_sets_all_fields
  - test_with_circuit_breaker_duration_parsing
  - test_with_backpressure_sets_fields
  - test_with_workers_sets_count
  - test_with_shutdown_timeout_parses_string
  - test_with_exceptions_sets_all_fields
  - test_with_parallel_sink_writes_enables
  - test_with_metrics_enables
  - test_with_error_deduplication_sets_window
  - test_with_diagnostics_sets_output
  - test_with_app_name_sets_name
  - test_with_strict_mode_enables
  - test_with_unhandled_exception_capture_enables
  - test_all_methods_are_chainable
  - test_builder_produces_equivalent_settings

### Contract Tests

- `tests/integration/test_builder_settings_contract.py`
  - test_builder_config_creates_valid_settings
  - test_builder_circuit_breaker_matches_settings_behavior

---

## Definition of Done

### Code Complete

- [ ] All 12+ new builder methods implemented
- [ ] All methods return `Self` for chaining
- [ ] All methods have docstrings with examples
- [ ] Helper method for duration parsing added if needed

### Quality Assurance

- [ ] Unit tests for each new method
- [ ] Contract tests verifying Settings equivalence
- [ ] `ruff check` passes
- [ ] `mypy` passes
- [ ] Parity test for CoreSettings passes

### Documentation

- [ ] Docstrings complete for all new methods
- [ ] CHANGELOG updated

---

## Risks / Rollback

### Risks

1. **Risk:** Duration parsing inconsistency with existing methods
   - **Mitigation:** Reuse `_parse_duration` from `core.types`

2. **Risk:** Method naming conflicts with future Settings fields
   - **Mitigation:** Follow established patterns from design doc

### Rollback Plan

If issues occur:
1. Revert builder.py changes
2. Methods are additive, no breaking changes to existing API

---

## Related Stories

- **Depends on:** Story 10.22 - Builder API Parity Foundation
- **Enables:** Story 10.27 - Builder API Parity Enforcement (partial)
- **Related:** Story 10.24 - Builder Cloud Sink Methods
- **Related:** Story 10.25 - Builder Filter & Processor Methods

---

## Code Review

**Date:** 2026-01-21
**Reviewer:** Claude
**Verdict:** OK to PR

### Summary

Reviewed 12 new builder methods for CoreSettings fields with 47 unit tests and Settings equivalence contract tests.

### AC Verification

| Criterion | Evidence |
|-----------|----------|
| AC1: Circuit Breaker | `builder.py:288-310` + `test_builder_core_settings.py:405-437` |
| AC2: Backpressure | `builder.py:312-329` + `test_builder_core_settings.py:438-455` |
| AC3: Worker Config | `builder.py:331-356` + `test_builder_core_settings.py:457-475` |
| AC4: Exception Config | `builder.py:358-385` + `test_builder_core_settings.py:477-500` |
| AC5: Performance Config | `builder.py:387-406` + `test_builder_core_settings.py:502-520` |
| AC6: All Chainable | `test_builder_core_settings.py:379-399` |
| AC7: Parity Test | `test_builder_parity.py:252-273` passes |

### Quality Gates

- [x] ruff check passed
- [x] ruff format passed
- [x] mypy passed
- [x] Coverage 93%
- [x] No weak assertions
- [x] No dead code
- [x] Builder parity check passed

---

## Change Log

| Date | Change | Author |
|------|--------|--------|
| 2026-01-21 | Code review passed | Claude |
| 2026-01-20 | Initial draft | Claude |
