---
phase: 02-in-memory-backend-and-owner-security
plan: 03
type: execute
wave: 2
depends_on:
  - 02-01
files_modified:
  - crates/pmcp-tasks/tests/store_tests.rs
  - crates/pmcp-tasks/tests/security_tests.rs
  - crates/pmcp-tasks/tests/property_tests.rs
autonomous: true
requirements:
  - TEST-04
  - TEST-06
  - TEST-07

must_haves:
  truths:
    - "Store CRUD tests verify create, get, update_status, set_variables, set_result, get_result, list, cancel, cleanup_expired"
    - "Pagination tests verify cursor-based list with limits and multiple pages"
    - "TTL tests verify default TTL applied, max TTL rejected, and expired task cleanup"
    - "Concurrent access tests verify multiple tokio tasks can create/read/update without data corruption"
    - "Owner isolation tests verify cross-owner get/update/cancel/list all return NotFound or empty results"
    - "Anonymous rejection test verifies allow_anonymous=false rejects local/empty owner"
    - "Max tasks enforcement test verifies ResourceExhausted at limit"
    - "UUID entropy test verifies task IDs are unique across 1000+ creations"
    - "Property tests verify state machine invariants, variable merge semantics, task ID uniqueness, and owner isolation under arbitrary inputs"
  artifacts:
    - path: "crates/pmcp-tasks/tests/store_tests.rs"
      provides: "TEST-04 comprehensive store CRUD, pagination, TTL, and concurrent access tests"
      contains: "mod store_tests"
    - path: "crates/pmcp-tasks/tests/security_tests.rs"
      provides: "TEST-06 owner isolation, anonymous rejection, max tasks, UUID entropy tests"
      contains: "mod security_tests"
    - path: "crates/pmcp-tasks/tests/property_tests.rs"
      provides: "TEST-07 property tests extending Phase 1 with store-level invariants"
      contains: "proptest!"
  key_links:
    - from: "crates/pmcp-tasks/tests/store_tests.rs"
      to: "crates/pmcp-tasks/src/store/memory.rs"
      via: "exercises all InMemoryTaskStore methods"
      pattern: "InMemoryTaskStore::new"
    - from: "crates/pmcp-tasks/tests/security_tests.rs"
      to: "crates/pmcp-tasks/src/security.rs"
      via: "tests TaskSecurityConfig enforcement through the store"
      pattern: "TaskSecurityConfig"
    - from: "crates/pmcp-tasks/tests/property_tests.rs"
      to: "crates/pmcp-tasks/src/store/memory.rs"
      via: "property tests against InMemoryTaskStore with arbitrary inputs"
      pattern: "proptest!"
---

<objective>
Write comprehensive tests for the in-memory store, security enforcement, and property-based invariants.

Purpose: Phase 2 introduces server-side state and cross-user isolation -- the first security boundary in the SDK. These tests prove that the store works correctly under normal use, rejects unauthorized access, respects resource limits, and maintains invariants under arbitrary inputs. This is the safety net for the entire task system.

Output: Three test files covering store CRUD/pagination/TTL/concurrency (TEST-04), security enforcement (TEST-06), and property-based invariants (TEST-07). The existing `property_tests.rs` from Phase 1 is extended (not replaced) with new store-level property 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/02-in-memory-backend-and-owner-security/02-CONTEXT.md
@.planning/phases/02-in-memory-backend-and-owner-security/02-RESEARCH.md
@.planning/phases/02-in-memory-backend-and-owner-security/02-01-SUMMARY.md

Source files:
@crates/pmcp-tasks/src/store/memory.rs (InMemoryTaskStore to test)
@crates/pmcp-tasks/src/store/mod.rs (TaskStore trait, StoreConfig, ListTasksOptions, TaskPage)
@crates/pmcp-tasks/src/security.rs (TaskSecurityConfig, resolve_owner_id)
@crates/pmcp-tasks/src/error.rs (TaskError variants to assert)
@crates/pmcp-tasks/src/types/task.rs (TaskStatus)
@crates/pmcp-tasks/tests/property_tests.rs (existing Phase 1 property tests to extend)
</context>

<tasks>

<task type="auto">
  <name>Task 1: Write store CRUD, pagination, TTL, and concurrency tests (TEST-04)</name>
  <files>crates/pmcp-tasks/tests/store_tests.rs</files>
  <action>
Create `crates/pmcp-tasks/tests/store_tests.rs` as an integration test file.

All tests use `#[tokio::test]`. Create a helper to build a store with `allow_anonymous: true` for test convenience (most tests use "test-owner" as owner).

Organize into `mod` blocks. Use `pretty_assertions::assert_eq` imported per mod block (Phase 1 convention).

**mod crud_tests:**
- `test_create_returns_working_task` -- new task has status Working, correct owner, non-empty task_id
- `test_create_assigns_uuid_v4_task_id` -- task_id is 36 chars with hyphens
- `test_create_sets_poll_interval` -- poll_interval matches configured default
- `test_get_returns_created_task` -- create then get returns same task_id, owner, status
- `test_get_nonexistent_returns_not_found` -- get with bad task_id returns NotFound
- `test_update_status_working_to_completed` -- transition works, status and last_updated_at change
- `test_update_status_with_message` -- status_message is set
- `test_update_status_invalid_transition_rejected` -- completed->working returns InvalidTransition
- `test_set_variables_stores_values` -- set variables, get shows them in record.variables
- `test_set_variables_null_deletes_key` -- set key, then set null for that key, key is removed
- `test_set_variables_merges_with_existing` -- set {a:1}, then set {b:2}, both present
- `test_set_result_and_get_result` -- set_result then get_result returns the value
- `test_get_result_not_ready_for_working_task` -- get_result on working task returns NotReady
- `test_complete_with_result_atomic` -- sets both status and result atomically, verifiable via get + get_result
- `test_cancel_transitions_to_cancelled` -- cancel() works from working state
- `test_cancel_terminal_task_rejected` -- cancel completed task returns InvalidTransition

**mod pagination_tests:**
- `test_list_returns_owner_tasks_only` -- create tasks for 2 owners, list returns only requested owner's
- `test_list_empty_for_new_owner` -- list for non-existent owner returns empty page
- `test_list_with_limit` -- create 5 tasks, list with limit=2 returns 2 with next_cursor
- `test_list_pagination_with_cursor` -- get page 1, use cursor for page 2, verify no overlap
- `test_list_sorted_newest_first` -- create tasks, verify ordering
- `test_list_default_limit` -- list without explicit limit uses backend default (50)

**mod ttl_tests:**
- `test_create_with_default_ttl` -- no TTL specified, task gets config.default_ttl_ms applied
- `test_create_with_explicit_ttl` -- custom TTL applied, task.ttl matches
- `test_create_rejects_ttl_above_max` -- TTL > max_ttl_ms returns error (not clamped)
- `test_expired_task_readable_via_get` -- force expiry into past, get() still returns the record (per locked decision)
- `test_expired_task_rejects_update_status` -- expired task returns Expired error on update_status
- `test_expired_task_rejects_set_variables` -- expired task returns Expired on set_variables
- `test_cleanup_expired_removes_expired_tasks` -- create, force expiry, cleanup, get returns NotFound
- `test_cleanup_expired_returns_count` -- cleanup returns number of removed tasks
- `test_cleanup_expired_preserves_non_expired` -- non-expired tasks remain after cleanup

**mod concurrency_tests:**
- `test_concurrent_creates_all_succeed` -- spawn 10 tokio tasks, each creates 1 task, all succeed, total count is 10
- `test_concurrent_updates_no_data_loss` -- create task, spawn 5 tasks each setting different variables, all variables present after
- `test_concurrent_reads_during_writes` -- spawn readers and writers concurrently, no panics or errors

Note: Use `tokio::spawn` for concurrency tests. Wrap store in `Arc` for sharing. Use `tokio::task::JoinSet` or `futures::future::join_all` for awaiting concurrent tasks.
  </action>
  <verify>
`cargo test --package pmcp-tasks --test store_tests` passes all tests.
Test count is 25+.
  </verify>
  <done>
store_tests.rs contains 25+ integration tests organized into crud_tests, pagination_tests, ttl_tests, and concurrency_tests modules. All CRUD operations verified. Pagination with cursor verified. TTL enforcement (default, max rejection, expired readable, expired mutation rejected, cleanup) verified. Concurrent access verified with tokio::spawn.
  </done>
</task>

<task type="auto">
  <name>Task 2: Write security tests (TEST-06) and extend property tests (TEST-07)</name>
  <files>crates/pmcp-tasks/tests/security_tests.rs, crates/pmcp-tasks/tests/property_tests.rs</files>
  <action>
**Create `crates/pmcp-tasks/tests/security_tests.rs`** (TEST-06):

All tests `#[tokio::test]`. Use `pretty_assertions::assert_eq` per mod block.

**mod owner_isolation_tests:**
- `test_get_with_wrong_owner_returns_not_found` -- create as owner A, get as owner B returns NotFound (never OwnerMismatch)
- `test_update_status_wrong_owner_returns_not_found` -- create as A, update as B returns NotFound
- `test_set_variables_wrong_owner_returns_not_found` -- create as A, set_variables as B returns NotFound
- `test_set_result_wrong_owner_returns_not_found` -- create as A, set_result as B returns NotFound
- `test_get_result_wrong_owner_returns_not_found` -- create as A, complete as A, get_result as B returns NotFound
- `test_cancel_wrong_owner_returns_not_found` -- create as A, cancel as B returns NotFound
- `test_complete_with_result_wrong_owner_returns_not_found` -- create as A, complete_with_result as B returns NotFound
- `test_list_scoped_to_owner` -- create tasks for owners A, B, C. list(A) returns only A's tasks.
- `test_error_does_not_leak_owner_info` -- verify the NotFound error message does NOT contain any reference to the actual owner. The error must be indistinguishable from "task does not exist."

**mod anonymous_access_tests:**
- `test_anonymous_rejected_when_not_allowed` -- allow_anonymous=false, create with DEFAULT_LOCAL_OWNER returns error
- `test_anonymous_rejected_with_empty_owner` -- allow_anonymous=false, create with "" returns error
- `test_anonymous_allowed_when_configured` -- allow_anonymous=true, create with DEFAULT_LOCAL_OWNER succeeds
- `test_authenticated_owner_always_allowed` -- allow_anonymous=false, create with "user-123" succeeds

**mod resource_limit_tests:**
- `test_max_tasks_per_owner_enforced` -- set max_tasks_per_owner=3, create 3 tasks, 4th returns ResourceExhausted
- `test_max_tasks_per_owner_scoped_to_owner` -- owner A at limit, owner B can still create
- `test_max_tasks_per_owner_freed_after_cleanup` -- create max, expire them, cleanup, can create again
- `test_variable_size_limit_enforced` -- set large variable exceeding max_variable_size_bytes, returns VariableSizeExceeded

**mod uuid_entropy_tests:**
- `test_task_ids_unique_across_1000_creations` -- create 1000 tasks, collect task_ids into HashSet, assert set.len() == 1000
- `test_task_id_is_valid_uuid_v4` -- parse task_id with uuid::Uuid::parse_str, verify it is v4

**Extend `crates/pmcp-tasks/tests/property_tests.rs`** (TEST-07):

Add new property tests BELOW the existing Phase 1 tests (do NOT modify or remove existing tests). Add a clear section comment `// === Phase 2: Store-level property tests ===`.

Use proptest with `#[tokio::test]` where needed. Since proptest does not natively support async, use `tokio::runtime::Runtime` inside proptest closures:
```rust
proptest! {
    #[test]
    fn prop_name(input in strategy) {
        let rt = tokio::runtime::Runtime::new().unwrap();
        rt.block_on(async {
            // async test body
        });
    }
}
```

**New property tests:**
- `prop_state_machine_transitions_consistent_through_store` -- for arbitrary valid transition sequences, InMemoryTaskStore accepts them all and final status matches
- `prop_variable_merge_null_deletes_and_preserves_others` -- for arbitrary HashMap<String, Value> with some null values, after set_variables the nulls are absent and non-nulls are present
- `prop_task_ids_always_unique` -- create N tasks (N in 1..50), all task_ids are unique
- `prop_owner_isolation_holds_for_arbitrary_owners` -- for arbitrary (owner_a, owner_b) where a != b, task created by a returns NotFound for b

Create proptest strategies:
- `arb_owner()` -- non-empty alphanumeric strings (1..20 chars), excluding DEFAULT_LOCAL_OWNER to avoid anonymous confusion
- `arb_variable_map()` -- HashMap<String, Value> with 0..10 entries, values are strings, numbers, bools, or null
- `arb_valid_transition_sequence()` -- Vec of (TaskStatus, TaskStatus) pairs that are valid per the state machine (reuse existing Phase 1 TaskStatus strategy)
  </action>
  <verify>
`cargo test --package pmcp-tasks --test security_tests` passes all tests.
`cargo test --package pmcp-tasks --test property_tests` passes all tests (Phase 1 + Phase 2).
`cargo test --package pmcp-tasks` passes all tests (full suite, no regressions).
  </verify>
  <done>
security_tests.rs contains 18+ tests covering owner isolation (all operations return NotFound for wrong owner, error does not leak info), anonymous access (rejected/allowed based on config), resource limits (max tasks enforced per owner, variable size limits), and UUID entropy (uniqueness across 1000 creations, valid v4 format). property_tests.rs extended with 4 new property tests verifying state machine consistency through the store, variable merge semantics with null-deletion, task ID uniqueness, and owner isolation under arbitrary inputs. All existing Phase 1 property tests still pass.
  </done>
</task>

</tasks>

<verification>
```bash
# All test files pass individually
cargo test --package pmcp-tasks --test store_tests
cargo test --package pmcp-tasks --test security_tests
cargo test --package pmcp-tasks --test property_tests
cargo test --package pmcp-tasks --test context_tests  # From Plan 02 (verify no regression)

# Full test suite
cargo test --package pmcp-tasks

# Zero clippy warnings
cargo clippy --package pmcp-tasks -- -D warnings

# Test count check (should be significantly higher than Phase 1's 91)
cargo test --package pmcp-tasks 2>&1 | grep "test result"
```
</verification>

<success_criteria>
1. store_tests.rs has 25+ tests covering CRUD, pagination, TTL, and concurrency
2. security_tests.rs has 18+ tests covering owner isolation, anonymous access, resource limits, UUID entropy
3. property_tests.rs has Phase 1 tests + 4 new Phase 2 property tests, all passing
4. No test uses `OwnerMismatch` in assertions (only `NotFound` for wrong owner)
5. TTL tests verify rejection (not clamping) of over-max values
6. Full test suite passes with zero failures
</success_criteria>

<output>
After completion, create `.planning/phases/02-in-memory-backend-and-owner-security/02-03-SUMMARY.md`
</output>
