# Story 5.16: Add Filter to Plugin Type Metadata Validation

## Status: Complete

## Priority: Critical

## Estimated Effort: Small (< 1 day)

## Dependencies: None

## Epic: Plugin System Consistency

---

## Context

The `PluginMetadata` Pydantic model validates the `plugin_type` field against a set of valid types. However, the filter plugin type was added in Story 5.2 but the metadata validation was not updated to include "filter" as a valid type.

This means external plugins declaring `plugin_type: "filter"` will fail validation when using the `PluginMetadata` model.

---

## Problem Statement

Current validation in `src/fapilog/plugins/metadata.py`:

```python
@field_validator("plugin_type")
@classmethod
def validate_plugin_type(cls, v: str) -> str:
    """Validate plugin type."""
    valid_types = {"sink", "processor", "enricher", "redactor"}
    if v not in valid_types:
        raise ValueError(f"Invalid plugin type: {v}. Must be one of: {valid_types}")
    return v
```

The `valid_types` set is missing `"filter"`, despite filters being a fully supported plugin type with:

- `BaseFilter` protocol in `fapilog.plugins.filters`
- Six built-in filter implementations
- Entry point group `fapilog.filters`
- Full documentation in `docs/plugins/filters.md`

---

## Acceptance Criteria

### AC1: Add Filter to Valid Types

- [ ] Add `"filter"` to the `valid_types` set in `PluginMetadata.validate_plugin_type()`
- [ ] Ensure validation passes for `plugin_type: "filter"`

### AC2: Update Related Documentation

- [ ] Verify `docs/plugins/authoring.md` lists filter as a valid plugin type
- [ ] Verify `docs/plugins/contracts-and-versioning.md` includes BaseFilter

### AC3: Add Test Coverage

- [ ] Add unit test for filter plugin type validation
- [ ] Add test that all built-in filter PLUGIN_METADATA passes validation

---

## Technical Design

### 1. Update Metadata Validation

```python
# src/fapilog/plugins/metadata.py

@field_validator("plugin_type")
@classmethod
def validate_plugin_type(cls, v: str) -> str:
    """Validate plugin type."""
    valid_types = {"sink", "processor", "enricher", "redactor", "filter"}
    if v not in valid_types:
        raise ValueError(f"Invalid plugin type: {v}. Must be one of: {valid_types}")
    return v
```

### 2. Update create_plugin_metadata Helper

Ensure the docstring mentions filter:

```python
def create_plugin_metadata(
    name: str,
    version: str,
    plugin_type: str,
    entry_point: str,
    description: str = "",
    author: str = "",
    **kwargs: Any,
) -> PluginMetadata:
    """
    Helper function to create plugin metadata with sensible defaults.

    Args:
        name: Plugin name
        version: Plugin version
        plugin_type: Plugin type (sink, processor, enricher, redactor, filter)
        entry_point: Entry point for plugin loading
        description: Plugin description
        author: Plugin author
        **kwargs: Additional metadata fields

    Returns:
        PluginMetadata instance
    """
```

---

## Test Plan

### Unit Tests

**test_plugin_metadata.py**

```python
import pytest
from fapilog.plugins.metadata import PluginMetadata, PluginCompatibility


def test_filter_plugin_type_is_valid():
    """Filter should be a valid plugin type."""
    metadata = PluginMetadata(
        name="test_filter",
        version="1.0.0",
        description="Test filter",
        author="Test",
        plugin_type="filter",
        entry_point="test:TestFilter",
        compatibility=PluginCompatibility(min_fapilog_version="0.4.0"),
    )
    assert metadata.plugin_type == "filter"


def test_all_builtin_filter_metadata_validates():
    """All built-in filter PLUGIN_METADATA should pass validation."""
    from fapilog.plugins.filters.level import PLUGIN_METADATA as level_meta
    from fapilog.plugins.filters.sampling import PLUGIN_METADATA as sampling_meta
    from fapilog.plugins.filters.rate_limit import PLUGIN_METADATA as rate_meta
    from fapilog.plugins.filters.adaptive_sampling import PLUGIN_METADATA as adaptive_meta
    from fapilog.plugins.filters.trace_sampling import PLUGIN_METADATA as trace_meta
    from fapilog.plugins.filters.first_occurrence import PLUGIN_METADATA as first_meta

    for meta in [level_meta, sampling_meta, rate_meta, adaptive_meta, trace_meta, first_meta]:
        assert meta["plugin_type"] == "filter"
        # Should not raise
        PluginMetadata(
            name=meta["name"],
            version=meta["version"],
            description=meta.get("description", ""),
            author=meta.get("author", ""),
            plugin_type=meta["plugin_type"],
            entry_point=meta["entry_point"],
            compatibility=PluginCompatibility(
                min_fapilog_version=meta.get("compatibility", {}).get("min_fapilog_version", "0.4.0")
            ),
        )


@pytest.mark.parametrize("plugin_type", ["sink", "processor", "enricher", "redactor", "filter"])
def test_all_valid_plugin_types(plugin_type: str):
    """All documented plugin types should be valid."""
    metadata = PluginMetadata(
        name="test",
        version="1.0.0",
        description="Test",
        author="Test",
        plugin_type=plugin_type,
        entry_point="test:Test",
        compatibility=PluginCompatibility(min_fapilog_version="0.3.0"),
    )
    assert metadata.plugin_type == plugin_type
```

---

## Documentation Updates

### docs/plugins/authoring.md

Update the PLUGIN_METADATA section:

````markdown
## PLUGIN_METADATA

Each module must export a `PLUGIN_METADATA` mapping with at least:

```python
PLUGIN_METADATA = {
  "name": "my_plugin",
  "version": "1.2.3",
  "plugin_type": "sink",  # sink|processor|enricher|redactor|filter
  ...
}
```
````

**Valid plugin types:**

- `sink` - Output destinations for log entries
- `processor` - Transform serialized bytes
- `enricher` - Add fields to log events
- `redactor` - Sanitize sensitive data
- `filter` - Drop or transform events before enrichment

```

---

## Backwards Compatibility

- No breaking changes
- Existing filter plugins with `plugin_type: "filter"` will now pass validation
- Existing plugins are not affected

---

## Related Stories

- Story 5.2: Add filter plugin type
- Story 5.3: Add sampling and rate limiting plugins
- Story 5.1: Fix PLUGIN_METADATA name inconsistencies

```
