Skip to content
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 69 additions & 0 deletions docs/audit/PHASE8_AUDIT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# PHASE 8 AUDIT: Pagination & Count Semantics

## 1. Scope & Objectives
**Phase:** Phase 8 (Pagination & Count Semantics)
**Focus:** `paginate()`, `count()`, `PaginationResultDTO` consistency across MySQL, MongoDB, and Redis.
**Sources:** `docs/adr/ADR-010.md`, `docs/adr/ADR-011.md`, `src/Generic/*`.

## 2. ADR Compliance Matrix

| Component | Requirement | Status | Notes |
|-----------|-------------|--------|-------|
| **ADR-010** | Return `PaginationResultDTO` only | ✅ PASS | All `paginate*` methods return strict DTO. |
| **ADR-010** | Structure (items, total, page, perPage, pages) | ✅ PASS | Handled via `PaginationHelper::buildMeta` and DTO constructor. |
| **ADR-010** | Deterministic (`pages = ceil(total/perPage)`) | ✅ PASS | Centralized in `PaginationHelper`. |
| **ADR-011** | `count()` is filter-scoped | ✅ PASS | Redis implementation correctly fails fast for unsupported filtered counts. |
| **ADR-011** | Redis Safety (SCAN-based, Bounded) | ✅ PASS | `RedisOps::keys` enforces safety limits. |
| **Phase 6** | `LimitOffsetValidator` Usage | ✅ PASS | Validated in all `paginate*` methods. |

## 3. Findings

### 3.1. Redis Filtered Count (Documented Limitation)
**Observation:**
`GenericRedisRepository::count(array $filters)` throws `RepositoryException` when filters are provided, while `paginateBy($filters)` successfully returns a total.

**Rationale:**
Implementing `count($filters)` via `findBy($filters)` (scanning and filtering all keys) encourages inefficient and dangerous usage patterns on Redis. While `paginateBy` *must* perform this operation to fulfill the pagination contract, exposing it as a standalone `count` method implies a lightweight operation that does not exist in Redis.

**Compliance:**
This adheres to ADR-011's requirement:
> "If the operation exceeds safety limits [or capabilities]: The method **must fail fast**... Silent partial counts are **forbidden**."

The exception is the correct, safe behavior. The fact that `PaginationResultDTO.total` is available during pagination is a side-effect of the necessary in-memory processing for that specific context, not a guarantee that `count($filters)` is globally supported.

### 3.2. Missing Documentation
The current documentation does not explicitly state that `count($filters)` is unsupported for Redis, nor does it explain the nuance of `PaginationResultDTO.total` in this context. This could lead to developer confusion.

## 4. Verdict

**Status:** ⚠️ **BLOCKED (Documentation Required)**

The implementation is correct and safe, but the specific limitations of Redis `count()` must be explicitly documented to prevent misuse and clarify the API contract.

---

## 5. Blueprint for Remediation

### Goal
Document the Redis-specific limitations for `count()` and the behavior of `PaginationResultDTO.total`.

### Action Plan
1. **Do NOT modify source code.** The current exception in `GenericRedisRepository::count` is correct.
2. **Update `docs/README.full.md`** (or create `docs/phases/README.phase8.md` if preferred) with the following clarifications.

### Suggested Documentation

**Location:** `docs/README.full.md` -> **Redis Repository Behavior** section (or similar).

> ### ⚠️ Redis `count()` Limitations
>
> Unlike MySQL or MongoDB, the **Redis Repository does NOT support filtered counts**.
>
> * `count()` (no arguments) returns the total number of keys matching the repository prefix.
> * `count(['status' => 'active'])` will **throw a RepositoryException**.
>
> **Why?**
> Redis is a key-value store, not a relational database. Counting filtered items requires fetching and deserializing *every* item in the repository, which is a high-cost operation.
>
> **Note on Pagination:**
> When using `paginateBy($filters)`, the `PaginationResultDTO` **will** contain a correct `total`. This is because pagination in Redis *already* incurs the cost of fetching and filtering items to determine the result set. This `total` is a byproduct of that expensive operation and is provided for convenience within the pagination context only. Do not rely on `count($filters)` for standalone metrics.
Loading