# Story 10.27: Builder API Parity Enforcement

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

---

## Context / Background

Enforcement should be implemented **early** (immediately after 10.22) to guide the quality of subsequent builder stories (10.23-10.26). By having the Claude skills updated and pre-commit scaffolded before implementing the bulk of builder methods, we ensure consistent patterns from the start.

**Incremental Implementation Approach:**

| Phase | When | What |
|-------|------|------|
| **Phase 1** | After 10.22 | Update Claude skills (implement, code-review) with parity rules |
| **Phase 2** | After 10.22 | Scaffold pre-commit script with initial Core coverage |
| **Phase 3** | During 10.23-10.26 | Extend mappings and script as each story completes |
| **Phase 4** | After 10.26 | Finalize CI integration and documentation |

This "enforcement-first" approach means stories 10.23-10.26 are implemented with guardrails already in place, rather than adding enforcement after the fact.

**Configuration categories requiring parity:**

| Category | Settings Location | Builder Pattern | Story |
|----------|-------------------|-----------------|-------|
| Core | `CoreSettings` | `with_*()` | 10.23 |
| Cloud Sinks | `CloudWatchSinkSettings`, `LokiSinkSettings`, `PostgresSinkSettings` | `add_*()` | 10.24 |
| Filters | `FilterConfig.*` | `with_sampling()`, `with_rate_limit()`, etc. | 10.25 |
| Processors | `ProcessorConfigSettings.*` | `with_size_guard()` | 10.25 |
| Advanced | `SinkRoutingSettings`, `RedactorConfig.*`, `PluginsSettings` | `with_routing()`, `with_field_mask()`, etc. | 10.26 |

**Enforcement layers:**

1. **CI Tests** - Parity test from 10.22 runs on every PR (all categories)
2. **Pre-commit Hook** - Fast local check before commits (all categories)
3. **Claude Skills** - AI-assisted enforcement during implementation (guidance for all categories)
4. **Parameter Mapping Registry** - Documented mappings between builder params and settings fields

This story implements the pre-commit hook, Claude skill updates, and parameter mapping registry. The CI test was established in Story 10.22.

**Reference documents:**
- `docs/architecture/builder-design-patterns.md` (from 10.22) - Naming conventions, validation strategy
- `docs/architecture/builder-api-gaps.md` (from 10.22) - Full gap audit

---

## Scope (In / Out)

### In Scope

- Pre-commit hook script for builder parity (all configuration categories)
- Parameter mapping registry (`scripts/builder_param_mappings.py`)
- Update `implement` skill with configuration parity rules for all categories
- Update `code-review` skill with parity validation for all categories
- Documentation of enforcement process

### Out of Scope

- Schema-driven code generation (evaluated in 10.22, optional future work)
- Enforcement for third-party plugins
- Breaking changes to existing hooks
- Env var alias enforcement (automatic via Pydantic, separate concern)

---

## Acceptance Criteria

### AC1: Pre-commit Hook Blocks Core Settings Violations

**Description:** A pre-commit hook prevents commits that add CoreSettings fields without builder methods.

**Validation:**
```bash
# When adding a new field to CoreSettings without builder method:
git add src/fapilog/core/settings.py
git commit -m "add new field"
# Output:
# Builder Parity Check Failed
# CoreSettings fields without builder methods: ['new_field']
# Add a builder method or add to CORE_EXCLUSIONS if truly internal.
```

### AC2: Pre-commit Hook Blocks Sink Settings Violations

**Description:** The hook detects missing parameters in sink builder methods.

**Validation:**
```bash
# When adding a new field to CloudWatchSinkSettings without add_cloudwatch() param:
git add src/fapilog/core/settings.py
git commit -m "add cloudwatch field"
# Output:
# Builder Parity Check Failed
# CloudWatchSinkSettings fields without add_cloudwatch() coverage: ['new_field']
```

### AC3: Pre-commit Hook Blocks Filter/Processor Violations

**Description:** The hook detects missing filter and processor builder methods.

**Validation:**
```bash
# When adding a new filter to FilterConfig without builder method:
git add src/fapilog/core/settings.py
git commit -m "add new filter"
# Output:
# Builder Parity Check Failed
# FilterConfig types without builder methods: ['new_filter']
```

### AC4: Pre-commit Hook Fast Execution

**Description:** The hook completes in under 2 seconds for typical changes.

**Validation:**
```bash
time .git/hooks/pre-commit
# real < 2.0s
```

### AC5: Parameter Mapping Registry Exists

**Description:** A registry documents all builder param → settings field mappings.

**Validation:**
```python
# scripts/builder_param_mappings.py contains:

SINK_PARAM_MAPPINGS = {
    "add_cloudwatch": {
        "log_group": "log_group_name",      # Builder param → Settings field
        "stream": "log_stream_name",
        "region": "region",
        "batch_size": "batch_size",
        "batch_timeout": "batch_timeout_seconds",
        "create_group": "create_log_group",
        "create_stream": "create_log_stream",
        # ... all mappings documented
    },
    "add_loki": { ... },
    "add_postgres": {
        "min_pool": "min_pool_size",
        "max_pool": "max_pool_size",
        "pool_acquire_timeout": "pool_acquire_timeout",
        # ... all mappings documented
    },
}

# Pre-commit validates these mappings are complete
```

### AC6: Implement Skill Updated

**Description:** The `implement` skill includes configuration parity rules for ALL categories.

**Validation:**
```markdown
# .claude/skills/implement/SKILL.md includes:

## Configuration Parity Rule

Covers:
- CoreSettings → with_*() methods
- SinkSettings → add_*() methods
- FilterConfig → with_sampling(), with_rate_limit(), etc.
- ProcessorConfig → with_size_guard()
- Advanced → with_routing(), with_field_mask(), with_plugins()

With checklist and examples for each category.
```

### AC7: Code Review Skill Updated

**Description:** The `code-review` skill validates configuration parity for ALL categories.

**Validation:**
```markdown
# .claude/skills/code-review/SKILL.md includes:

## Configuration Parity Validation

Trigger: PRs modifying settings.py or builder.py

Checks for each category:
- CoreSettings ↔ with_*() methods
- SinkSettings ↔ add_*() methods
- FilterConfig ↔ filter builder methods
- ProcessorConfig ↔ processor builder methods
- Advanced settings ↔ routing/redactor/plugin methods

Blocking issues:
- Any settings field without corresponding builder coverage
- Missing parameter in builder method
- Missing unit test for new builder method
```

### AC8: Exclusion List Mechanism

**Description:** Intentionally uncovered fields can be excluded from enforcement, with documented rationale.

**Validation:**
```python
# scripts/check_builder_parity.py

# Each category has its own exclusion list with rationale
CORE_EXCLUSIONS = {
    "schema_version",  # Internal versioning, not user-configurable
    "benchmark_file_path",  # Test-only field
    "sensitive_fields_policy",  # Policy hint, not runtime config
    "redactors_order",  # Managed internally by with_redaction methods
}

SINK_EXCLUSIONS = {
    "model_config",  # Pydantic internal
}

# Excluded fields don't trigger failures
```

---

## Implementation Notes

### File Changes

```
scripts/check_builder_parity.py (NEW - comprehensive parity checker)
scripts/builder_param_mappings.py (NEW - parameter mapping registry)
.pre-commit-config.yaml (MODIFIED - add local hook)
.claude/skills/implement/SKILL.md (MODIFIED - add parity rules for all categories)
.claude/skills/code-review/SKILL.md (MODIFIED - add parity validation for all categories)
docs/contributing/configuration-parity.md (NEW - contributor guide)
```

### Pre-commit Hook Script

```python
#!/usr/bin/env python3
"""Check builder API parity with Settings - ALL categories.

This script ensures all Settings fields have corresponding builder methods.
Run as a pre-commit hook or manually via: python scripts/check_builder_parity.py

Checks:
1. CoreSettings fields → with_*() methods
2. Sink settings → add_*() method parameters
3. Filter/Processor settings → with_*() methods
4. Advanced settings → routing/redactor/plugin methods
"""

import sys
from pathlib import Path

# Import mapping registry
from builder_param_mappings import (
    CORE_COVERAGE,
    SINK_PARAM_MAPPINGS,
    FILTER_COVERAGE,
    PROCESSOR_COVERAGE,
    ADVANCED_COVERAGE,
    CORE_EXCLUSIONS,
    SINK_EXCLUSIONS,
)


def get_model_fields(model_class) -> set[str]:
    """Extract field names from a Pydantic model."""
    return set(model_class.model_fields.keys()) - {"model_config"}


def check_core_settings() -> list[str]:
    """Check CoreSettings parity."""
    from fapilog.core.settings import CoreSettings

    fields = get_model_fields(CoreSettings) - CORE_EXCLUSIONS
    covered = set()
    for method_fields in CORE_COVERAGE.values():
        covered.update(method_fields)

    missing = fields - covered
    if missing:
        return [f"CoreSettings fields without builder methods: {sorted(missing)}"]
    return []


def check_sink_settings() -> list[str]:
    """Check sink settings parity (CloudWatch, Loki, Postgres)."""
    from fapilog.core.settings import (
        CloudWatchSinkSettings,
        LokiSinkSettings,
        PostgresSinkSettings,
    )

    errors = []
    sink_checks = [
        ("add_cloudwatch", CloudWatchSinkSettings),
        ("add_loki", LokiSinkSettings),
        ("add_postgres", PostgresSinkSettings),
    ]

    for method_name, settings_class in sink_checks:
        fields = get_model_fields(settings_class) - SINK_EXCLUSIONS
        if method_name not in SINK_PARAM_MAPPINGS:
            errors.append(f"Missing SINK_PARAM_MAPPINGS for {method_name}")
            continue

        covered = set(SINK_PARAM_MAPPINGS[method_name].values())
        missing = fields - covered
        if missing:
            errors.append(
                f"{settings_class.__name__} fields without {method_name}() coverage: "
                f"{sorted(missing)}"
            )

    return errors


def check_filter_settings() -> list[str]:
    """Check filter builder method coverage."""
    # FilterConfig uses dict[str, Any] for each filter, so we check
    # that each filter type has a with_*() method
    expected_filters = {
        "sampling", "rate_limit", "adaptive_sampling",
        "trace_sampling", "first_occurrence"
    }
    covered = set(FILTER_COVERAGE.keys())
    missing = expected_filters - covered

    if missing:
        return [f"FilterConfig types without builder methods: {sorted(missing)}"]
    return []


def check_processor_settings() -> list[str]:
    """Check processor builder method coverage."""
    from fapilog.core.settings import SizeGuardSettings

    errors = []
    if "size_guard" not in PROCESSOR_COVERAGE:
        errors.append("Missing PROCESSOR_COVERAGE for size_guard")
    else:
        fields = get_model_fields(SizeGuardSettings)
        covered = set(PROCESSOR_COVERAGE["size_guard"].values())
        missing = fields - covered
        if missing:
            errors.append(
                f"SizeGuardSettings fields without with_size_guard() coverage: "
                f"{sorted(missing)}"
            )

    return errors


def check_advanced_settings() -> list[str]:
    """Check advanced settings (routing, redactors, plugins)."""
    from fapilog.core.settings import (
        SinkRoutingSettings,
        RedactorFieldMaskSettings,
        RedactorRegexMaskSettings,
    )

    errors = []
    advanced_checks = [
        ("with_routing", SinkRoutingSettings, ["enabled"]),  # enabled is implicit
        ("with_field_mask", RedactorFieldMaskSettings, []),
        ("with_regex_mask", RedactorRegexMaskSettings, []),
    ]

    for method_name, settings_class, extra_exclusions in advanced_checks:
        if method_name not in ADVANCED_COVERAGE:
            errors.append(f"Missing ADVANCED_COVERAGE for {method_name}")
            continue

        fields = get_model_fields(settings_class) - set(extra_exclusions)
        covered = set(ADVANCED_COVERAGE[method_name].values())
        missing = fields - covered
        if missing:
            errors.append(
                f"{settings_class.__name__} fields without {method_name}() coverage: "
                f"{sorted(missing)}"
            )

    return errors


def main() -> int:
    # Add src to path for imports
    sys.path.insert(0, str(Path("src").resolve()))

    all_errors = []
    all_errors.extend(check_core_settings())
    all_errors.extend(check_sink_settings())
    all_errors.extend(check_filter_settings())
    all_errors.extend(check_processor_settings())
    all_errors.extend(check_advanced_settings())

    if all_errors:
        print("Builder Parity Check Failed\n")
        for error in all_errors:
            print(f"  • {error}")
        print("\nEither:")
        print("1. Add builder method/parameter coverage")
        print("2. Add to appropriate exclusion list with rationale")
        print("\nSee: docs/contributing/configuration-parity.md")
        return 1

    print("Builder parity check passed (all categories)")
    return 0


if __name__ == "__main__":
    sys.exit(main())
```

### Parameter Mapping Registry

```python
# scripts/builder_param_mappings.py
"""Registry of builder parameter → settings field mappings.

This file serves as:
1. Documentation of all param name translations
2. Source of truth for parity checking
3. Reference for implementers adding new settings

Maintained manually; validated by pre-commit hook.
"""

# === EXCLUSIONS (with rationale) ===

CORE_EXCLUSIONS = {
    "schema_version",           # Internal versioning
    "benchmark_file_path",      # Test-only field
    "sensitive_fields_policy",  # Policy hint, not runtime config
    "redactors_order",          # Managed internally by redactor methods
    "enable_redactors",         # Controlled via redactor list being non-empty
}

SINK_EXCLUSIONS = {
    "model_config",  # Pydantic internal
}


# === CORE SETTINGS COVERAGE ===
# Maps builder method → list of CoreSettings fields it covers

CORE_COVERAGE = {
    "with_level": ["log_level"],
    "with_queue_size": ["max_queue_size"],
    "with_batch_size": ["batch_max_size"],
    "with_batch_timeout": ["batch_timeout_seconds"],
    "with_context": ["default_bound_context"],
    "with_enrichers": ["enrichers"],
    "with_filters": ["filters"],
    "with_redaction": ["redactors"],
    "with_circuit_breaker": [
        "sink_circuit_breaker_enabled",
        "sink_circuit_breaker_failure_threshold",
        "sink_circuit_breaker_recovery_timeout_seconds",
    ],
    "with_backpressure": ["backpressure_wait_ms", "drop_on_full"],
    "with_workers": ["worker_count"],
    "with_shutdown_timeout": ["shutdown_timeout_seconds"],
    "with_exceptions": [
        "exceptions_enabled",
        "exceptions_max_frames",
        "exceptions_max_stack_chars",
    ],
    "with_parallel_sink_writes": ["sink_parallel_writes"],
    "with_metrics": ["enable_metrics"],
    "with_error_deduplication": ["error_dedupe_window_seconds"],
    "with_diagnostics": ["internal_logging_enabled", "diagnostics_output"],
    "with_app_name": ["app_name"],
    "with_strict_mode": ["strict_envelope_mode"],
    "with_unhandled_exception_capture": ["capture_unhandled_enabled"],
    "with_context_binding": ["context_binding_enabled"],
    "with_serialize_in_flush": ["serialize_in_flush"],
    "with_resource_pool": [
        "resource_pool_max_size",
        "resource_pool_acquire_timeout_seconds",
    ],
    "with_redaction_guardrails": [
        "redaction_max_depth",
        "redaction_max_keys_scanned",
    ],
    "with_fallback_redaction": ["fallback_redact_mode"],
}


# === SINK PARAMETER MAPPINGS ===
# Maps builder param name → settings field name

SINK_PARAM_MAPPINGS = {
    "add_cloudwatch": {
        # Builder param: Settings field
        "log_group": "log_group_name",
        "stream": "log_stream_name",
        "region": "region",
        "endpoint_url": "endpoint_url",
        "batch_size": "batch_size",
        "batch_timeout": "batch_timeout_seconds",
        "max_retries": "max_retries",
        "retry_delay": "retry_base_delay",
        "create_group": "create_log_group",
        "create_stream": "create_log_stream",
        "circuit_breaker": "circuit_breaker_enabled",
        "circuit_breaker_threshold": "circuit_breaker_threshold",
    },
    "add_loki": {
        "url": "url",
        "tenant_id": "tenant_id",
        "labels": "labels",
        "label_keys": "label_keys",
        "batch_size": "batch_size",
        "batch_timeout": "batch_timeout_seconds",
        "timeout": "timeout_seconds",
        "max_retries": "max_retries",
        "retry_delay": "retry_base_delay",
        "auth_username": "auth_username",
        "auth_password": "auth_password",
        "auth_token": "auth_token",
        "circuit_breaker": "circuit_breaker_enabled",
        "circuit_breaker_threshold": "circuit_breaker_threshold",
    },
    "add_postgres": {
        "dsn": "dsn",
        "host": "host",
        "port": "port",
        "database": "database",
        "user": "user",
        "password": "password",
        "table": "table_name",
        "schema": "schema_name",
        "batch_size": "batch_size",
        "batch_timeout": "batch_timeout_seconds",
        "max_retries": "max_retries",
        "retry_delay": "retry_base_delay",
        "min_pool": "min_pool_size",
        "max_pool": "max_pool_size",
        "pool_acquire_timeout": "pool_acquire_timeout",
        "create_table": "create_table",
        "use_jsonb": "use_jsonb",
        "include_raw_json": "include_raw_json",
        "extract_fields": "extract_fields",
        "circuit_breaker": "circuit_breaker_enabled",
        "circuit_breaker_threshold": "circuit_breaker_threshold",
    },
}


# === FILTER COVERAGE ===
# Maps filter type → builder method that covers it

FILTER_COVERAGE = {
    "sampling": "with_sampling",
    "rate_limit": "with_rate_limit",
    "adaptive_sampling": "with_adaptive_sampling",
    "trace_sampling": "with_trace_sampling",
    "first_occurrence": "with_first_occurrence",
}


# === PROCESSOR COVERAGE ===
# Maps processor → param:field mappings

PROCESSOR_COVERAGE = {
    "size_guard": {
        "max_bytes": "max_bytes",
        "action": "action",
        "preserve_fields": "preserve_fields",
    },
}


# === ADVANCED COVERAGE ===
# Maps method → param:field for routing, redactors, plugins

ADVANCED_COVERAGE = {
    "with_routing": {
        "rules": "rules",
        "fallback": "fallback_sinks",
        "overlap": "overlap",
    },
    "with_field_mask": {
        "fields": "fields_to_mask",
        "mask": "mask_string",
        "block_on_failure": "block_on_unredactable",
        "max_depth": "max_depth",
        "max_keys": "max_keys_scanned",
    },
    "with_regex_mask": {
        "patterns": "patterns",
        "mask": "mask_string",
        "block_on_failure": "block_on_unredactable",
        "max_depth": "max_depth",
        "max_keys": "max_keys_scanned",
    },
    "with_url_credential_redaction": {
        "max_string_length": "max_string_length",
    },
    "with_plugins": {
        "enabled": "enabled",
        "allow_external": "allow_external",
        "allowlist": "allowlist",
        "denylist": "denylist",
        "validation_mode": "validation_mode",
    },
}
```

### Pre-commit Configuration

```yaml
# .pre-commit-config.yaml addition
- repo: local
  hooks:
    - id: builder-parity
      name: Check Builder/Settings Parity
      entry: python scripts/check_builder_parity.py
      language: python
      files: ^src/fapilog/(builder|core/settings)\.py$
      pass_filenames: false
```

### Implement Skill Update

Add to `.claude/skills/implement/SKILL.md`:

```markdown
## Configuration Parity Rule

**Reference:** Stories 10.22-10.28, `docs/architecture/builder-design-patterns.md`

When adding or modifying any configuration in fapilog, follow the appropriate pattern based on category:

### Configuration Categories

| Category | Settings Location | Builder Pattern | Example |
|----------|-------------------|-----------------|---------|
| Core | `CoreSettings` | `with_*()` | `with_workers()` |
| Cloud Sinks | `CloudWatch/Loki/PostgresSinkSettings` | `add_*()` | `add_cloudwatch()` |
| Filters | `FilterConfig.*` | `with_*()` | `with_sampling()` |
| Processors | `ProcessorConfigSettings.*` | `with_*()` | `with_size_guard()` |
| Advanced | `SinkRoutingSettings`, `RedactorConfig.*` | `with_*()` | `with_routing()` |

### Required Steps (All Categories)

1. **Settings**: Add field to appropriate Settings class with docstring
2. **Builder**: Add/update corresponding builder method
3. **Mapping**: Update `scripts/builder_param_mappings.py`
4. **Test**: Add unit test for builder method
5. **Verify**: Run `python scripts/check_builder_parity.py`

### Category-Specific Patterns

#### Core Settings (`with_*` methods)

```python
# CoreSettings field
worker_count: int = Field(default=1, description="Number of workers")

# Builder method
def with_workers(self, count: int = 1) -> LoggerBuilder:
    self._config.setdefault("core", {})["worker_count"] = count
    return self
```

#### Cloud Sinks (`add_*` methods)

```python
# CloudWatchSinkSettings field
log_group_name: str = Field(...)

# Builder method parameter (note: simplified param name)
def add_cloudwatch(self, log_group: str, ...) -> LoggerBuilder:
    config["log_group_name"] = log_group  # Maps param → field
```

**Param naming:** Use human-friendly names, document mapping in `builder_param_mappings.py`

#### Filters/Processors

```python
# with_sampling() enables 'sampling' filter AND configures it
def with_sampling(self, rate: float = 1.0, *, seed: int | None = None):
    filters = self._config.setdefault("core", {}).setdefault("filters", [])
    if "sampling" not in filters:
        filters.append("sampling")
    filter_config = self._config.setdefault("filter_config", {})
    filter_config["sampling"] = {"sample_rate": rate, "seed": seed}
```

### Consistency Requirements

- **Duration fields**: Accept both strings ("30s") and floats via `_parse_duration()`
- **Size fields**: Accept both strings ("10 MB") and ints
- **Boolean fields**: Use `enabled` as parameter name
- **Return type**: Always `Self` for chaining

### Checklist Before Complete

- [ ] Settings field added with description
- [ ] Builder method added with docstring and example
- [ ] Parameter mapping added to `scripts/builder_param_mappings.py`
- [ ] Unit test for builder method
- [ ] Parity test passes: `python scripts/check_builder_parity.py`
```

### Code Review Skill Update

Add to `.claude/skills/code-review/SKILL.md`:

```markdown
## Configuration Parity Validation

**Reference:** Stories 10.22-10.28, `scripts/builder_param_mappings.py`

### Trigger

PRs modifying ANY of:
- `src/fapilog/core/settings.py` (all Settings classes)
- `src/fapilog/builder.py`
- `scripts/builder_param_mappings.py`

### Category-Specific Checks

#### 1. CoreSettings Changes

- [ ] New field has `with_*()` method in builder
- [ ] Method added to `CORE_COVERAGE` in mappings
- [ ] Or field added to `CORE_EXCLUSIONS` with rationale

#### 2. Sink Settings Changes (CloudWatch, Loki, Postgres)

- [ ] New field has parameter in corresponding `add_*()` method
- [ ] Parameter mapping in `SINK_PARAM_MAPPINGS`
- [ ] Duration params accept "30s" strings
- [ ] Size params accept "10 MB" strings

#### 3. Filter/Processor Changes

- [ ] New filter type has `with_*()` method
- [ ] Filter added to `FILTER_COVERAGE`
- [ ] Method enables filter AND configures it

#### 4. Advanced Settings (Routing, Redactors, Plugins)

- [ ] New fields covered by appropriate `with_*()` method
- [ ] Mapping in `ADVANCED_COVERAGE`

### Consistency Checks (All Categories)

- [ ] Duration fields: `_parse_duration()` used for string support
- [ ] Size fields: Settings `SizeField` type handles string parsing
- [ ] Boolean fields: `enabled` parameter name
- [ ] All methods return `Self`
- [ ] Docstrings include usage example

### Blocking Issues (P0)

- Settings field without builder coverage (check all categories)
- Builder method parameter without Settings field mapping
- Missing entry in `builder_param_mappings.py`
- Missing unit test for new builder method

### Should Fix (P1)

- Parameter name doesn't match convention
- Missing `_parse_duration()` for duration field
- Docstring missing example

### Verification Command

```bash
python scripts/check_builder_parity.py
```

### Non-Blocking (P2)

- Docstring wording improvements
- Additional test edge cases
```

---

## Tasks

### Phase 1: Pre-commit Hook

- [ ] Create `scripts/check_builder_parity.py` (comprehensive, all categories)
- [ ] Implement Pydantic model_fields extraction for all Settings classes
- [ ] Test CoreSettings parity check
- [ ] Test sink settings parity check (CloudWatch, Loki, Postgres)
- [ ] Test filter/processor parity check
- [ ] Test advanced settings parity check
- [ ] Add to `.pre-commit-config.yaml`
- [ ] Test hook locally with various violation scenarios

### Phase 2: Parameter Mapping Registry

- [ ] Create `scripts/builder_param_mappings.py`
- [ ] Document all CORE_COVERAGE mappings
- [ ] Document all SINK_PARAM_MAPPINGS (with param → field translations)
- [ ] Document FILTER_COVERAGE and PROCESSOR_COVERAGE
- [ ] Document ADVANCED_COVERAGE
- [ ] Add exclusion lists with rationale

### Phase 3: Claude Skills

- [ ] Update `implement` skill with category-specific parity rules
- [ ] Update `code-review` skill with category-specific validation
- [ ] Add references to design patterns doc and stories
- [ ] Test skill updates with sample scenarios

### Phase 4: Documentation

- [ ] Create `docs/contributing/configuration-parity.md`
- [ ] Document each category's requirements
- [ ] Document exclusion process with examples
- [ ] Document parameter mapping conventions

### Phase 5: Integration

- [ ] Verify hook integrates with existing pre-commit
- [ ] Verify CI test (from 10.22) still passes
- [ ] End-to-end test: add CoreSettings field → verify enforcement
- [ ] End-to-end test: add sink settings field → verify enforcement
- [ ] End-to-end test: add filter type → verify enforcement

---

## Tests

### Manual Tests

**CoreSettings:**
- Add CoreSettings field without builder → hook blocks
- Add CoreSettings field with builder method → hook passes
- Add field to CORE_EXCLUSIONS → hook passes

**Sink Settings:**
- Add CloudWatchSinkSettings field without add_cloudwatch() param → hook blocks
- Add field with param AND mapping → hook passes
- Add to SINK_EXCLUSIONS → hook passes

**Filters/Processors:**
- Add new filter to FilterConfig without builder method → hook blocks
- Add with_* method AND FILTER_COVERAGE entry → hook passes

**Advanced:**
- Add SinkRoutingSettings field without with_routing() coverage → hook blocks

### Automated Tests

- `tests/test_builder_parity.py` (from 10.22) remains the comprehensive CI gate
- Pre-commit hook provides fast local feedback (subset of CI checks)

---

## Definition of Done

### Code Complete

- [ ] Pre-commit hook script checks ALL categories (Core, Sinks, Filters, Processors, Advanced)
- [ ] Parameter mapping registry created with all mappings
- [ ] Hook added to `.pre-commit-config.yaml`
- [ ] `implement` skill updated with category-specific guidance
- [ ] `code-review` skill updated with category-specific validation

### Quality Assurance

- [ ] Hook completes in < 2 seconds
- [ ] Hook correctly identifies missing coverage for each category
- [ ] Existing pre-commit hooks still work
- [ ] No false positives for intentionally excluded fields

### Documentation

- [ ] Contributing guide covers all categories
- [ ] Parameter mapping conventions documented
- [ ] Exclusion rationale documented
- [ ] CHANGELOG updated

---

## Risks / Rollback

### Risks

1. **Risk:** Hook slows down commits
   - **Mitigation:** Uses Pydantic model_fields (fast), only checks when settings/builder changed

2. **Risk:** False positives block valid commits
   - **Mitigation:** Category-specific exclusion lists with documented rationale; easy bypass with `--no-verify`

3. **Risk:** Parameter mapping registry becomes stale
   - **Mitigation:** Pre-commit validates mappings are complete; CI test catches any drift

4. **Risk:** New sink/filter types not covered by enforcement
   - **Mitigation:** Checklist in skills explicitly mentions updating mappings; code-review skill validates

### Rollback Plan

If issues occur:
1. Remove hook from `.pre-commit-config.yaml`
2. CI test (from 10.22) remains as safety net
3. Skill updates are non-blocking guidance

---

## Related Stories

- **Depends on:** Story 10.22 (Builder API Parity Foundation)
- **Guides:** Stories 10.23-10.26 (implemented with enforcement in place)
- **Enables:** Sustainable builder maintenance, consistent implementation quality
- **Related:** Story 10.28 - Builder API Documentation

---

## Code Review

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

### Summary

Implemented builder API parity enforcement with pre-commit hook, parameter mapping registry, Claude skill updates, and contributor documentation.

### AC Verification

| Criterion | Evidence |
|-----------|----------|
| AC1: Pre-commit blocks Core violations | `scripts/check_builder_parity.py:33-50` - `check_core_settings()` |
| AC2: Pre-commit blocks Sink violations | `scripts/check_builder_parity.py:53-88` - `check_sink_settings()` |
| AC3: Pre-commit blocks Filter/Processor violations | `scripts/check_builder_parity.py:91-141` |
| AC4: Fast execution (< 2s) | Verified: 0.315s execution time |
| AC5: Parameter Mapping Registry | `scripts/builder_param_mappings.py` - SINK_PARAM_MAPPINGS |
| AC6: Implement skill updated | `.claude/skills/implement/SKILL.md:113-189` |
| AC7: Code-review skill updated | `.claude/skills/code-review/SKILL.md:106-169` |
| AC8: Exclusion list mechanism | `scripts/builder_param_mappings.py:14-27` |

### Quality Gates

- [x] ruff check passed
- [x] ruff format passed
- [x] mypy passed
- [x] 25 tests passing
- [x] No weak assertions
- [x] No dead code

---

## Change Log

| Date | Change | Author |
|------|--------|--------|
| 2026-01-20 | Initial draft | Claude |
| 2026-01-20 | Expanded to cover all config categories (Core, Sinks, Filters, Processors, Advanced); added parameter mapping registry; expanded skill guidance | Claude |
| 2026-01-20 | Changed to enforcement-first approach: implement early (after 10.22) to guide 10.23-10.26 quality; updated dependencies and added incremental implementation phases | Claude |
