# Story 4.31: Fix MemoryMappedPersistence Sink Registration

**Status:** Implemented  
**Priority:** Critical  
**Depends on:** None  
**Effort:** 0.5 days

---

## Problem Statement

The `MemoryMappedPersistence` class is registered as a built-in sink in the plugin loader, but it **does not implement the `BaseSink` protocol**:

```python
# Current registration in sinks/__init__.py
register_builtin(
    "fapilog.sinks",
    "mmap_persistence",
    MemoryMappedPersistence,
    aliases=["mmap-persistence"],
)
```

### What's Wrong

The `MemoryMappedPersistence` class has a different API than `BaseSink`:

| BaseSink Protocol          | MemoryMappedPersistence | Status         |
| -------------------------- | ----------------------- | -------------- |
| `async write(entry: dict)` | ❌ Not implemented      | **Missing**    |
| `async start()`            | `async open()`          | Different name |
| `async stop()`             | `async close()`         | Different name |
| `async health_check()`     | ✅ Implemented          | OK             |

Instead, `MemoryMappedPersistence` provides:

- `async open()` / `async close()` - Context manager lifecycle
- `async append(data: BytesLike)` - Low-level byte append
- `async append_line(data: BytesLike)` - Convenience for JSONL

### Impact

1. **Users cannot use it as a sink**: Attempting to configure `mmap_persistence` as a sink will fail with `AttributeError: 'MemoryMappedPersistence' object has no attribute 'write'`

2. **Misleading plugin catalog**: The `docs/plugin-guide.md` lists it as a sink

3. **Protocol validation fails**: `validate_sink(MemoryMappedPersistence())` will report errors

---

## Options

### Option A: Remove from Sink Registration (Recommended)

Remove `MemoryMappedPersistence` from `BUILTIN_SINKS` and document it as a **building block** for custom sinks, not a sink itself.

**Pros:**

- Accurate representation of what it does
- No API changes needed
- Fast to implement

**Cons:**

- Minor breaking change for anyone expecting it as a sink (unlikely given it never worked)

### Option B: Create a Wrapper Sink

Create `MemoryMappedFileSink` that wraps `MemoryMappedPersistence` and implements `BaseSink`:

```python
class MemoryMappedFileSink:
    name = "mmap_file"

    def __init__(self, path: str | Path, **kwargs):
        self._mmap = MemoryMappedPersistence(path, **kwargs)

    async def start(self) -> None:
        await self._mmap.open()

    async def write(self, entry: dict) -> None:
        data = json.dumps(entry).encode('utf-8')
        await self._mmap.append_line(data)

    async def stop(self) -> None:
        await self._mmap.close()

    async def health_check(self) -> bool:
        return await self._mmap.health_check()
```

**Pros:**

- Provides mmap-backed sink functionality
- Keeps the building block available

**Cons:**

- More code to maintain
- Duplicates some functionality of `RotatingFileSink`

### Option C: Modify MemoryMappedPersistence to Implement BaseSink

Add `write()`, `start()`, `stop()` methods directly.

**Pros:**

- Single class
- Registry entry works as expected

**Cons:**

- Conflates building block with sink
- Awkward API (both `open`/`close` and `start`/`stop`)

---

## Recommendation

**Option A (Remove from Registration)** with clear documentation that `MemoryMappedPersistence` is a building block for performance-critical custom sinks.

---

## Implementation Plan

### Phase 1: Remove Registration

**File: `src/fapilog/plugins/sinks/__init__.py`**

Remove the registration:

```python
# DELETE these lines:
# register_builtin(
#     "fapilog.sinks",
#     "mmap_persistence",
#     MemoryMappedPersistence,
#     aliases=["mmap-persistence"],
# )
```

Keep the exports in `__all__` for users who want to use it as a building block.

### Phase 2: Update Documentation

**File: `docs/plugin-guide.md`**

Remove from the sink table or change to note it's a building block.

**File: `docs/api-reference/plugins/sinks.md`**

Add a section explaining `MemoryMappedPersistence` is a building block:

````markdown
## Building Blocks

### MemoryMappedPersistence

A low-level memory-mapped file writer for custom zero-copy sinks.
Not a sink itself - use it to build performance-critical custom sinks.

```python
from fapilog.plugins.sinks import MemoryMappedPersistence

class MyMmapSink:
    name = "my_mmap"

    def __init__(self, path: str):
        self._mmap = MemoryMappedPersistence(path)

    async def start(self) -> None:
        await self._mmap.open()

    async def write(self, entry: dict) -> None:
        import json
        data = json.dumps(entry).encode()
        await self._mmap.append_line(data)

    async def stop(self) -> None:
        await self._mmap.close()
```
````

````

### Phase 3: Add Test

**File: `tests/unit/test_mmap_not_a_sink.py`**

```python
"""Test that MemoryMappedPersistence is NOT registered as a sink."""

from fapilog.plugins.loader import BUILTIN_SINKS


def test_mmap_not_in_builtin_sinks():
    """MemoryMappedPersistence should not be in sink registry."""
    assert "mmap_persistence" not in BUILTIN_SINKS
    assert "mmap-persistence" not in BUILTIN_SINKS
````

---

## Acceptance Criteria

- [ ] `MemoryMappedPersistence` is NOT registered in `BUILTIN_SINKS`
- [ ] `MemoryMappedPersistence` is still exported for use as a building block
- [ ] Documentation updated to clarify it's a building block
- [ ] Test verifies it's not in the sink registry
- [ ] Plugin catalog regenerated without `mmap_persistence` as a sink

---

## Files Changed

| File                                    | Change                         |
| --------------------------------------- | ------------------------------ |
| `src/fapilog/plugins/sinks/__init__.py` | Remove `register_builtin` call |
| `docs/plugin-guide.md`                  | Update plugin catalog          |
| `docs/api-reference/plugins/sinks.md`   | Add building blocks section    |
| `tests/unit/test_mmap_not_a_sink.py`    | New test file                  |
