# SPRINT-GAP-005: Entity Mixin Context Ordering

| Attribute | Value |
|-----------|-------|
| **Sprint** | GAP-005 |
| **Size** | S (Small) |
| **Owner** | TBD |
| **Phase** | Bug Fix |
| **Prerequisites** | None |

## Open Issues

- [ ] None - clear bug fix

## Validation Status

| Component | Exists | Needs Work |
|-----------|--------|------------|
| Entity CRUD | Yes | Context manager ordering inconsistent |
| Feature requirement check | Yes | Called outside context (safe today) |
| Similar patterns | No | Only entities uses this pattern |

## Objective

Align entity mixin with the standard async context pattern. Current ordering is safe today but inconsistent and could become brittle if feature checks evolve.

## Key Decisions

| Decision | Choice | Rationale |
|----------|--------|-----------|
| Fix approach | Move inside context | Matches other mixins' pattern |
| Audit scope | Completed | Only entities uses this ordering |

## What Already Exists

### Entity Mixin (`src/noteflow/grpc/_mixins/entities.py`)
- `ExtractEntities` - Uses NerService; no UoW
- `UpdateEntity` - Creates uow, checks feature, then enters context
- `DeleteEntity` - Same ordering as UpdateEntity

### Correct Pattern (from webhooks.py)

```python
async with self.create_repository_provider() as uow:
    await require_feature_webhooks(uow, context)
    # ... operations
```

## Identified Issue

### Context Manager Ordering Inconsistency in UpdateEntity/DeleteEntity (Low)

**Location**: `src/noteflow/grpc/_mixins/entities.py:114-120`

```python
async def UpdateEntity(self: ServicerHost, request, context):
    _meeting_id = await parse_meeting_id_or_abort(request.meeting_id, context)
    entity_id = await parse_entity_id(request.entity_id, context)

    uow = self.create_repository_provider()  # Created but not entered
    await require_feature_entities(uow, context)  # Used before __aenter__

    async with uow:  # Now entered
        # ...
```

**Problem**: The repository provider is created and used before entering its context:
1. `create_repository_provider()` returns an async context manager
2. `require_feature_entities(uow, context)` is called on it
3. The context manager is then entered with `async with uow:`

**Impact**:
- Today `require_feature_entities` only reads `supports_entities`, so no DB session is accessed
- If the check ever expands to touch repositories, the ordering could become a real bug
- This deviates from the pattern used elsewhere in mixins

### Same Issue in DeleteEntity

**Location**: `src/noteflow/grpc/_mixins/entities.py:156-162`

```python
uow = self.create_repository_provider()
await require_feature_entities(uow, context)

async with uow:
```

## Scope

### Task Breakdown

| Task | Effort | Description |
|------|--------|-------------|
| Fix UpdateEntity | S | Move require_feature inside context (consistency) |
| Fix DeleteEntity | S | Move require_feature inside context (consistency) |
| Audit all mixins | S | Completed: only entities uses this ordering |

### Files to Modify

- `src/noteflow/grpc/_mixins/entities.py`

## Fix Implementation

### UpdateEntity (Before)

```python
async def UpdateEntity(self: ServicerHost, request, context):
    _meeting_id = await parse_meeting_id_or_abort(request.meeting_id, context)
    entity_id = await parse_entity_id(request.entity_id, context)

    uow = self.create_repository_provider()
    await require_feature_entities(uow, context)

    async with uow:
        entity = await uow.entities.get(entity_id)
        # ...
```

### UpdateEntity (After)

```python
async def UpdateEntity(self: ServicerHost, request, context):
    _meeting_id = await parse_meeting_id_or_abort(request.meeting_id, context)
    entity_id = await parse_entity_id(request.entity_id, context)

    async with self.create_repository_provider() as uow:
        await require_feature_entities(uow, context)
        entity = await uow.entities.get(entity_id)
        # ...
```

### DeleteEntity (Same Fix)

```python
async def DeleteEntity(self: ServicerHost, request, context):
    _meeting_id = await parse_meeting_id_or_abort(request.meeting_id, context)
    entity_id = await parse_entity_id(request.entity_id, context)

    async with self.create_repository_provider() as uow:
        await require_feature_entities(uow, context)
        entity = await uow.entities.get(entity_id)
        # ...
```

## Migration Strategy

This is a bug fix with no migration needed. The fix is backward compatible.

## Deliverables

### Backend
- [ ] Fix `UpdateEntity` context manager usage
- [ ] Fix `DeleteEntity` context manager usage
- [ ] Audit all mixins for similar patterns

### Tests
- [ ] Unit test: UpdateEntity with feature disabled (should abort cleanly)
- [ ] Unit test: DeleteEntity with feature disabled (should abort cleanly)
- [ ] Integration test: Entity operations work correctly

## Test Strategy

### Test Cases

| Case | Input | Expected |
|------|-------|----------|
| UpdateEntity, feature disabled | Valid request, in-memory UoW | UNIMPLEMENTED error, no leak |
| UpdateEntity, feature enabled | Valid request, DB UoW | Success |
| DeleteEntity, feature disabled | Valid request, in-memory UoW | UNIMPLEMENTED error, no leak |
| DeleteEntity, feature enabled | Valid request, DB UoW | Success |

## Quality Gates

- [ ] No repository provider usage outside context manager
- [ ] If usage occurs outside context, it is limited to capability flags
- [ ] All mixins follow consistent pattern
- [ ] Tests pass for both DB and memory backends
- [ ] No resource warnings in logs

## Audit Checklist

Audit results (create_repository_provider used outside context only in entities; export uses factory injection by design):

- [x] `_mixins/meeting.py` - Correct usage
- [x] `_mixins/annotation.py` - Correct usage
- [x] `_mixins/export.py` - Uses UoW factory injection (no async with here)
- [x] `_mixins/summarization.py` - Correct usage
- [x] `_mixins/calendar.py` - No create_repository_provider usage
- [x] `_mixins/webhooks.py` - Correct usage
- [x] `_mixins/preferences.py` - Correct usage
- [x] `_mixins/sync.py` - Correct usage
- [x] `_mixins/observability.py` - No create_repository_provider usage
- [x] `_mixins/project/_mixin.py` - Correct usage
- [x] `_mixins/project/_membership.py` - Correct usage
- [x] `_mixins/diarization/_jobs.py` - Correct usage
- [x] `_mixins/streaming/_session.py` - Correct usage
