---
phase: 10-inmemory-backend-refactor
plan: 01
type: execute
wave: 1
depends_on: []
files_modified:
  - crates/pmcp-tasks/src/store/memory.rs
  - crates/pmcp-tasks/src/store/mod.rs
  - crates/pmcp-tasks/src/lib.rs
autonomous: true
requirements: [IMEM-01, IMEM-02, IMEM-03]

must_haves:
  truths:
    - "InMemoryBackend implements all 6 StorageBackend methods using DashMap"
    - "InMemoryTaskStore wraps GenericTaskStore<InMemoryBackend> with zero-arg new() constructor"
    - "Builder methods (with_config, with_security, with_poll_interval) work on InMemoryTaskStore"
    - "InMemoryTaskStore implements TaskStore via delegation to inner GenericTaskStore"
    - "All existing 200+ tests pass (assertions and coverage unchanged, test setup adapted)"
  artifacts:
    - path: "crates/pmcp-tasks/src/store/memory.rs"
      provides: "InMemoryBackend struct + StorageBackend impl + InMemoryTaskStore wrapper + TaskStore delegation"
      contains: "pub struct InMemoryBackend"
    - path: "crates/pmcp-tasks/src/store/mod.rs"
      provides: "Updated module docs removing Legacy section"
      contains: "GenericTaskStore"
    - path: "crates/pmcp-tasks/src/lib.rs"
      provides: "Re-export of InMemoryBackend"
      contains: "InMemoryBackend"
  key_links:
    - from: "crates/pmcp-tasks/src/store/memory.rs"
      to: "crates/pmcp-tasks/src/store/generic.rs"
      via: "InMemoryTaskStore.inner: GenericTaskStore<InMemoryBackend>"
      pattern: "GenericTaskStore<InMemoryBackend>"
    - from: "crates/pmcp-tasks/src/store/memory.rs"
      to: "crates/pmcp-tasks/src/store/backend.rs"
      via: "impl StorageBackend for InMemoryBackend"
      pattern: "impl StorageBackend for InMemoryBackend"
---

<objective>
Rewrite `memory.rs` to replace the current direct `TaskStore` implementation with:
1. `InMemoryBackend` implementing `StorageBackend` (dumb KV using DashMap)
2. `InMemoryTaskStore` as a thin wrapper around `GenericTaskStore<InMemoryBackend>` with all existing builder methods and a `TaskStore` delegation impl

All 200+ existing tests must pass with their assertions unchanged (test setup code may be adapted).

Purpose: Eliminate ~600 lines of duplicated domain logic in InMemoryTaskStore by delegating to GenericTaskStore.
Output: Rewritten `memory.rs` with InMemoryBackend + InMemoryTaskStore wrapper, passing all existing tests.
</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
@crates/pmcp-tasks/src/store/memory.rs
@crates/pmcp-tasks/src/store/generic.rs
@crates/pmcp-tasks/src/store/backend.rs
@crates/pmcp-tasks/src/store/mod.rs
@crates/pmcp-tasks/src/lib.rs
</context>

<tasks>

<task type="auto">
  <name>Task 1: Implement InMemoryBackend and rewrite InMemoryTaskStore as thin wrapper</name>
  <files>
    crates/pmcp-tasks/src/store/memory.rs
    crates/pmcp-tasks/src/store/mod.rs
    crates/pmcp-tasks/src/lib.rs
  </files>
  <action>
Rewrite `crates/pmcp-tasks/src/store/memory.rs` completely:

**Part A: InMemoryBackend (promote from TestBackend in generic.rs)**

Create `pub struct InMemoryBackend` with `DashMap<String, (Vec<u8>, u64)>` data field:
- `pub fn new() -> Self` — creates empty DashMap
- `impl Default` — delegates to `new()`
- `impl StorageBackend` with all 6 methods, byte-for-byte identical to TestBackend in `generic.rs` lines 626-730:
  - `get` — DashMap lookup, return NotFound if missing
  - `put` — read current version or default to 1, insert, return new version
  - `put_if_version` — CAS via `get_mut`, return VersionConflict on mismatch
  - `delete` — `remove`, return bool
  - `list_by_prefix` — iterate + filter by prefix
  - `cleanup_expired` — iterate, deserialize records, collect expired keys, remove
- Add `pub fn len(&self) -> usize` method on InMemoryBackend (wraps `self.data.len()`) — needed by tests that previously called `store.tasks.len()`
- Add `pub fn is_empty(&self) -> bool` method on InMemoryBackend (wraps `self.data.is_empty()`)

**Part B: InMemoryTaskStore as thin wrapper**

Create `pub struct InMemoryTaskStore` with single field `inner: GenericTaskStore<InMemoryBackend>`:
- `pub fn new() -> Self` — creates `GenericTaskStore::new(InMemoryBackend::new()).with_poll_interval(5000)` to preserve the legacy 5000ms default (per research recommendation, avoids test churn on ~4 poll_interval assertions)
- `pub fn with_config(mut self, config: StoreConfig) -> Self` — delegates to `self.inner = self.inner.with_config(config); self`
- `pub fn with_security(mut self, security: TaskSecurityConfig) -> Self` — delegates similarly
- `pub fn with_poll_interval(mut self, ms: u64) -> Self` — delegates similarly
- `impl Default` — delegates to `new()`
- `impl Debug` — derive or manual impl

Add `#[cfg(test)] pub fn backend(&self) -> &InMemoryBackend` accessor on InMemoryTaskStore — allows test code to access the backend for force-expiry and len() checks.

**Part C: TaskStore delegation impl**

Add `#[async_trait] impl TaskStore for InMemoryTaskStore` that delegates all 11 methods to `self.inner`:
- `create`, `get`, `update_status`, `set_variables`, `set_result`, `get_result`, `complete_with_result`, `list`, `cancel`, `cleanup_expired`, `config`

Each method is a one-line forwarding call (e.g., `self.inner.create(owner_id, request_method, ttl).await`).

**Part D: Adapt unit tests in memory.rs**

Keep all test assertions unchanged. Adapt test setup code:

1. **`store.tasks.len()` references** (lines 635, 642, 1302, 1314): Replace with `store.backend().len()` using the `#[cfg(test)]` accessor.

2. **`store.default_poll_interval` references** (lines 637, 644): Replace with assertions that use the TaskStore trait: create a task and check `record.task.poll_interval == Some(5000)`, OR access `store.backend()` is not needed here — the poll_interval field is private inside the wrapper. Instead, assert via creating a task. For the `new_creates_empty_store` test: assert `store.backend().is_empty()` and verify poll_interval by creating a task. For `default_delegates_to_new`: same approach.

3. **`store.config.max_variable_size_bytes` references** (line 653): Replace with `store.config().max_variable_size_bytes` (config() is available via TaskStore trait).

4. **`store.security.max_tasks_per_owner` references** (line 659): This tests the builder. Since `security` is now private inside GenericTaskStore inside the wrapper, the test should verify behavior (create max+1 tasks fails) rather than inspect the field. However, looking at the test `with_security_sets_security`, it directly accesses `store.security.max_tasks_per_owner`. Adapt this test to verify behavior: create tasks up to the limit and verify the next one fails. Similarly for `with_config_sets_config` — use `store.config()`.

5. **Force-expiry tests** (lines 801, 859, 1010, 1296): These use `store.tasks.get_mut(&task_id)` to set `expires_at` in the past. Replace with the backend-level approach:
   - Use `store.backend()` to get the InMemoryBackend
   - Use composite key: `use crate::store::backend::make_key; let key = make_key("owner-1", &created.task.task_id);`
   - Read the record: `let versioned = store.backend().get(&key).await.unwrap();` — BUT InMemoryBackend's `get` is async and we need access. Since `backend()` returns `&InMemoryBackend`, and `InMemoryBackend` is `StorageBackend`, we can call methods on it.
   - Deserialize: `let mut record: TaskRecord = serde_json::from_slice(&versioned.data).unwrap();`
   - Modify: `record.expires_at = Some(chrono::Utc::now() - chrono::Duration::seconds(10));`
   - Serialize: `let bytes = serde_json::to_vec(&record).unwrap();`
   - Write back: `store.backend().put_if_version(&key, &bytes, versioned.version).await.unwrap();`

6. **`cleanup_expired_keeps_non_expired` test** (line 1314): Replace `store.tasks.len()` with `store.backend().len()`.

**Part E: Update mod.rs**

In `crates/pmcp-tasks/src/store/mod.rs`, update the module-level doc comment:
- Remove the `# Legacy` section (lines 27-31) since InMemoryTaskStore is no longer a legacy direct implementor.
- Mention InMemoryBackend in the architecture docs.

**Part F: Update lib.rs re-exports**

In `crates/pmcp-tasks/src/lib.rs`, add `InMemoryBackend` to the re-exports:
- Add `pub use store::memory::InMemoryBackend;` (make it publicly accessible for downstream users who want to create `GenericTaskStore<InMemoryBackend>` with custom configurations).
  </action>
  <verify>
Run the full test suite:
```bash
cargo test -p pmcp-tasks --test-threads=1
```
All tests must pass. Also run:
```bash
cargo clippy -p pmcp-tasks -- -D warnings
cargo fmt -p pmcp-tasks --check
cargo doc -p pmcp-tasks --no-deps
```
Zero warnings, formatting clean, docs build.
  </verify>
  <done>
- `InMemoryBackend` exists as a public struct implementing `StorageBackend` with DashMap
- `InMemoryTaskStore` is a thin wrapper around `GenericTaskStore<InMemoryBackend>` with `new()`, `with_config()`, `with_security()`, `with_poll_interval()`, `Default`
- `InMemoryTaskStore` implements `TaskStore` via delegation
- All existing unit tests in memory.rs pass (assertions unchanged, setup adapted)
- All integration tests (store_tests, property_tests, security_tests, context_tests, lifecycle_integration, workflow_integration) pass unchanged
- Zero clippy warnings, clean formatting, docs build
  </done>
</task>

</tasks>

<verification>
```bash
# 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
cargo doc -p pmcp-tasks --no-deps

# Verify InMemoryBackend is publicly re-exported
cargo doc -p pmcp-tasks --no-deps 2>&1 | grep -v "^$" | head -5

# Run root-level integration tests that use InMemoryTaskStore
cargo test -p pmcp --test-threads=1 -- task
```
</verification>

<success_criteria>
- InMemoryBackend implements StorageBackend using DashMap<String, (Vec<u8>, u64)>
- InMemoryTaskStore is a thin wrapper around GenericTaskStore<InMemoryBackend>
- All builder methods (new, with_config, with_security, with_poll_interval) work
- TaskStore delegation impl forwards all 11 methods
- All 200+ existing tests pass with zero modifications to assertions
- Zero clippy warnings, clean formatting
</success_criteria>

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