---
phase: 10-inmemory-backend-refactor
plan: 02
type: execute
wave: 2
depends_on: ["10-01"]
files_modified:
  - crates/pmcp-tasks/src/store/generic.rs
autonomous: true
requirements: [TEST-01, IMEM-01]

must_haves:
  truths:
    - "Per-backend unit tests validate all 6 StorageBackend methods on InMemoryBackend"
    - "TestBackend in generic.rs is replaced by InMemoryBackend (single source of truth)"
    - "CasConflictBackend test wrapper uses InMemoryBackend instead of TestBackend"
    - "All generic.rs tests pass using InMemoryBackend"
  artifacts:
    - path: "crates/pmcp-tasks/src/store/memory.rs"
      provides: "Per-backend StorageBackend contract tests for InMemoryBackend"
      contains: "mod backend_tests"
    - path: "crates/pmcp-tasks/src/store/generic.rs"
      provides: "GenericTaskStore tests using InMemoryBackend instead of TestBackend"
      contains: "use crate::store::memory::InMemoryBackend"
  key_links:
    - from: "crates/pmcp-tasks/src/store/generic.rs"
      to: "crates/pmcp-tasks/src/store/memory.rs"
      via: "test code imports InMemoryBackend"
      pattern: "InMemoryBackend"
---

<objective>
Add per-backend StorageBackend contract tests for InMemoryBackend (TEST-01), and replace the TestBackend in generic.rs with InMemoryBackend so there is a single source of truth for the in-memory backend implementation.

Purpose: Validate InMemoryBackend's StorageBackend contract independently and eliminate the duplicated TestBackend.
Output: ~15 per-backend contract tests in memory.rs, generic.rs tests using InMemoryBackend.
</objective>

<execution_context>
@./.claude/get-shit-done/workflows/execute-plan.md
@./.claude/get-shit-done/templates/summary.md
</execution_context>

<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/STATE.md
@.planning/phases/10-inmemory-backend-refactor/10-CONTEXT.md
@.planning/phases/10-inmemory-backend-refactor/10-RESEARCH.md
@.planning/phases/10-inmemory-backend-refactor/10-01-SUMMARY.md
@crates/pmcp-tasks/src/store/memory.rs
@crates/pmcp-tasks/src/store/generic.rs
@crates/pmcp-tasks/src/store/backend.rs
</context>

<tasks>

<task type="auto">
  <name>Task 1: Add per-backend StorageBackend contract tests for InMemoryBackend</name>
  <files>crates/pmcp-tasks/src/store/memory.rs</files>
  <action>
Add a `#[cfg(test)] mod backend_tests` module inside `memory.rs` (separate from the existing `mod tests` which tests TaskStore behavior through the InMemoryTaskStore wrapper).

This module tests InMemoryBackend's StorageBackend trait implementation directly, covering all 6 methods with happy paths and error cases. Per CONTEXT.md: "full contract coverage of all 6 StorageBackend methods (get, put, put_if_version, delete, list_by_prefix, cleanup_expired) with happy paths and error cases."

Tests to create (approximately 15-18 tests):

**get:**
- `get_missing_key_returns_not_found` — `backend.get("nonexistent")` returns `Err(StorageError::NotFound { .. })`
- `get_returns_stored_data` — put then get, verify data and version match

**put:**
- `put_new_key_returns_version_1` — first put returns version 1
- `put_existing_key_increments_version` — second put returns version 2, data is updated
- `put_overwrites_data` — put twice with different data, get returns latest

**put_if_version:**
- `put_if_version_succeeds_on_match` — put, then put_if_version with correct version, returns incremented version
- `put_if_version_fails_on_mismatch` — put, then put_if_version with wrong version, returns `Err(StorageError::VersionConflict { .. })` with correct expected/actual
- `put_if_version_fails_on_missing_key` — put_if_version on key that was never put, returns `Err(StorageError::NotFound { .. })`
- `put_if_version_updates_data` — verify the data is actually updated after successful CAS

**delete:**
- `delete_existing_returns_true` — put then delete returns true
- `delete_missing_returns_false` — delete nonexistent key returns false
- `delete_then_get_returns_not_found` — put, delete, get returns NotFound

**list_by_prefix:**
- `list_by_prefix_returns_matching` — put keys "owner:a", "owner:b", "other:c", list prefix "owner:" returns 2 entries
- `list_by_prefix_empty_on_no_match` — list with unmatched prefix returns empty vec
- `list_by_prefix_returns_correct_data_and_versions` — verify VersionedRecord data and version fields

**cleanup_expired:**
- `cleanup_expired_removes_expired_records` — put a record with past expiry (create a TaskRecord with `expires_at` in the past, serialize to bytes, put), call cleanup_expired, verify returns 1 and get returns NotFound
- `cleanup_expired_keeps_non_expired_records` — put a record with future expiry, cleanup returns 0, get still works
- `cleanup_expired_returns_zero_on_empty` — empty backend, cleanup returns 0

All tests use `InMemoryBackend::new()` directly (not through InMemoryTaskStore). Use `#[tokio::test]` for async methods.

For cleanup_expired tests that need TaskRecord serialization:
```rust
use crate::domain::TaskRecord;
let mut record = TaskRecord::new("owner".to_string(), "tools/call".to_string(), Some(60_000));
record.expires_at = Some(chrono::Utc::now() - chrono::Duration::seconds(10)); // expired
let bytes = serde_json::to_vec(&record).unwrap();
backend.put("owner:task-id", &bytes).await.unwrap();
```
  </action>
  <verify>
```bash
cargo test -p pmcp-tasks store::memory::backend_tests --test-threads=1
```
All backend contract tests pass.
  </verify>
  <done>
- `mod backend_tests` exists in memory.rs with 15+ tests
- All 6 StorageBackend methods covered (happy paths + error cases)
- Tests use InMemoryBackend directly, not InMemoryTaskStore
- All tests pass
  </done>
</task>

<task type="auto">
  <name>Task 2: Replace TestBackend in generic.rs with InMemoryBackend</name>
  <files>crates/pmcp-tasks/src/store/generic.rs</files>
  <action>
In `crates/pmcp-tasks/src/store/generic.rs`, in the `#[cfg(test)] mod tests` section:

1. **Remove the entire TestBackend struct and its StorageBackend impl** (lines 618-730). This includes the `struct TestBackend`, `impl TestBackend`, and `#[async_trait] impl StorageBackend for TestBackend`.

2. **Add import for InMemoryBackend:**
   ```rust
   use crate::store::memory::InMemoryBackend;
   ```

3. **Update `test_store()` helper** to use InMemoryBackend:
   ```rust
   fn test_store() -> GenericTaskStore<InMemoryBackend> {
       GenericTaskStore::new(InMemoryBackend::new())
           .with_security(TaskSecurityConfig::default().with_allow_anonymous(true))
   }
   ```

4. **Update `store_with_max_tasks()` helper** similarly:
   ```rust
   fn store_with_max_tasks(max: usize) -> GenericTaskStore<InMemoryBackend> {
       GenericTaskStore::new(InMemoryBackend::new()).with_security(
           TaskSecurityConfig::default()
               .with_max_tasks_per_owner(max)
               .with_allow_anonymous(true),
       )
   }
   ```

5. **Update CasConflictBackend** to wrap `Arc<InMemoryBackend>` instead of `Arc<TestBackend>`:
   ```rust
   struct CasConflictBackend {
       inner: Arc<InMemoryBackend>,
   }
   ```
   Update the `cas_conflict_returns_concurrent_modification` test:
   ```rust
   let backend = Arc::new(InMemoryBackend::new());
   ```

6. **Update `cleanup_expired_delegates_to_backend` test** — the serialization helper calls use `GenericTaskStore::<TestBackend>::` which must become `GenericTaskStore::<InMemoryBackend>::`:
   ```rust
   let mut record = GenericTaskStore::<InMemoryBackend>::deserialize_record(&versioned.data).unwrap();
   ...
   let bytes = GenericTaskStore::<InMemoryBackend>::serialize_record(&record).unwrap();
   ```

7. **Update `update_status_rejects_expired_task` test** — same serialization helper update.

8. **Update `generic_task_store_as_dyn_task_store` test** — replace `TestBackend::new()` with `InMemoryBackend::new()`.

9. **Remove unused imports** that were only needed by TestBackend (check if `DashMap` import is still needed — it may be removed if only TestBackend used it).

Per CONTEXT.md locked decision: "Replace the TestBackend in generic.rs with the real InMemoryBackend after the refactor -- single source of truth, no duplicated test backend." The CasConflictBackend wrapper stays because it tests GenericTaskStore's CAS error handling, not a specific backend.
  </action>
  <verify>
```bash
# Run generic.rs tests
cargo test -p pmcp-tasks store::generic::tests --test-threads=1

# Full test suite
cargo test -p pmcp-tasks --test-threads=1

# Quality gates
cargo clippy -p pmcp-tasks -- -D warnings
cargo fmt -p pmcp-tasks --check
```
All tests pass. Zero clippy warnings. Clean formatting.
  </verify>
  <done>
- TestBackend struct and impl completely removed from generic.rs
- All generic.rs tests use InMemoryBackend
- CasConflictBackend wraps Arc<InMemoryBackend>
- No duplicated test backend code in the codebase
- All tests pass, zero warnings
  </done>
</task>

</tasks>

<verification>
```bash
# Full test suite for pmcp-tasks
cargo test -p pmcp-tasks --test-threads=1

# Quality gates
cargo clippy -p pmcp-tasks -- -D warnings
cargo fmt -p pmcp-tasks --check
cargo doc -p pmcp-tasks --no-deps

# Root-level tests that use InMemoryTaskStore
cargo test -p pmcp --test-threads=1 -- task

# Verify TestBackend no longer exists
grep -r "TestBackend" crates/pmcp-tasks/src/ && echo "FAIL: TestBackend still exists" || echo "PASS: TestBackend removed"

# Verify InMemoryBackend is used in generic.rs tests
grep "InMemoryBackend" crates/pmcp-tasks/src/store/generic.rs | head -5
```
</verification>

<success_criteria>
- Per-backend StorageBackend contract tests exist for InMemoryBackend (15+ tests covering all 6 methods)
- TestBackend completely removed from generic.rs
- All generic.rs tests use InMemoryBackend as the backend
- CasConflictBackend wraps InMemoryBackend (not TestBackend)
- Zero duplicated test backend implementations
- All tests pass, zero clippy warnings, clean formatting
</success_criteria>

<output>
After completion, create `.planning/phases/10-inmemory-backend-refactor/10-02-SUMMARY.md`
</output>
