# Enterprise Agile Platform — UAT & Logic Audit

**Audit date:** 2026-06-04  
**Auditor role:** Principal QA / PO / Architect (code + API + test evidence)  
**Scope:** Full lifecycle — Project Creation → Project Closure  
**Method:** Static logic review, API contract tracing, `tests/Feature/Projects/*` execution (41 passed), prior docs cross-check. **Did not trust UI, implementation reports, or tests alone.**

---

## Executive summary

The Enterprise Agile platform is **substantially implemented** in the Projects module (Laravel) and `resources/js/src/features/projects` (React). Core APIs for projects, backlog, sprints, board, capacity (team MD), burndown, velocity (completed MD), Gantt, risks, and milestones exist and pass feature tests.

**Production readiness: NOT READY.** Several user-facing flows are **partial or stubbed** (work item dependencies UI, subtasks, comments, history), dependency **completion is not enforced**, sprint/project closure has **no workflow gates**, notifications are **incomplete** (no dedicated health-changed type; velocity penalty bug), and **live end-to-end UAT was not executed** because no application server was reachable during this audit (`http://127.0.0.1:8000` down). Staging Playwright flows exist but require an external environment (`docs/STAGING_E2E_REPORT.md`).

**Verdict:** Safe for continued internal pilot; **not** safe for enterprise go-live tomorrow without remediation of Critical/High items below.

---

## Audit environment

| Item | Status |
|------|--------|
| Codebase | `/Users/cairocoder/sites/MVNexus` |
| PHPUnit `Projects` filter | 41 tests, 187 assertions — **all passed** |
| Live UI / browser UAT | **Not run** — backend not serving |
| Screenshots | **Not captured** — see note above; evidence is API/logic citations |
| Seeded personas (Ahmed/Mohamed/Sara, Technology dept) | **Not present** in seeders; demo uses ACME/IT-style data |

---

## Role mapping (audit personas → system)

| UAT persona | System implementation |
|-------------|----------------------|
| Super Admin | `User.is_super_admin` |
| Company Admin | Spatie `company.departments.manage` + same company |
| Department Head | `isDepartmentHead(department_id)` + company match |
| Project Owner | `project.owner_user_id` or `ProjectMemberRole::OWNER` |
| Team Lead | **`ProjectMemberRole::MANAGER`** (no separate “lead” role) |
| Collaborator | **`ProjectMemberRole::MEMBER`** |
| Viewer | **`ProjectMemberRole::VIEWER`** — read via `ProjectPolicy::view`; mutations require `update` / ticket policies |

There is **no** dedicated `projects.*` Spatie permission; access is company/department head + membership (`ProjectVisibilityService`).

---

## Phase findings

### Phase 1 — Business flow (project creation)

| Check | Result | Evidence |
|-------|--------|----------|
| Create project with name, due date, department | **Pass (API)** | `ProjectController::store`, `ProjectService::create` |
| Department scoping | **Pass** | `ProjectVisibilityService::canCreateInDepartment`, scoped list |
| Permissions | **Partial** | Create: super admin, company admin, dept head only; members cannot create projects |
| Notifications on create | **Partial** | `ProjectCreated` event; not full matrix verified |
| Audit logs | **Pass (model)** | `Project` uses `Auditable` trait |

**Gaps**

- **Medium:** No live validation of “MV Mobile Revamp / Technology / 31 Dec 2026” scenario with notifications + audit UI.
- **Low:** Project owner auto-added as member; department employees not auto-invited.

---

### Phase 2 — Backlog

| Check | Result | Evidence |
|-------|--------|----------|
| Epics CRUD | **Pass (API)** | `ProjectEpicController` |
| Backlog items (priority, MD, points, due, assignee) | **Pass (API)** | `ProjectBacklogController`, `estimate_md` validation |
| Epic grouping | **Pass** | `group_by=epic` in `ProjectBacklogService` |
| Drag/drop reorder | **Pass (API + UI code)** | `PATCH .../backlog/reorder`, `SortableBacklogList` |
| Reload persistence | **Pass (logic)** | `backlog_order` column on tickets |
| Collaborators on backlog cards | **Partial** | Ticket collaborator system exists globally; **not** surfaced as backlog “collaborators” in Projects UI |

**Gaps**

- **High:** Work item **Dependencies** tab is a **count-only stub** (no create/link UI on project work item page).
- **Medium:** Collaborators not first-class on agile backlog UX.

---

### Phase 3 — Sprint planning

| Check | Result | Evidence |
|-------|--------|----------|
| Create Sprint 1 / 2 | **Pass (API)** | `ProjectSprintController` |
| Per-user capacity (Ahmed 10 / Mohamed 8 / Sara 12 MD) | **Pass (API)** | `PUT .../capacity-allocations`, `SprintTeamCapacityService` |
| Move items to sprint (DnD) | **Pass (UI code)** | `SprintPlanningPage` + `moveTicketToSprint` |
| Sprint counters | **Partial** | Planning page shows committed/load; must confirm live refresh |
| Over capacity 20 MD / 35 MD assigned | **Partial** | Two capacity models (see below) |

**Capacity logic (critical nuance)**

1. **Team capacity** (`SprintTeamCapacityService`): sums MD for tickets **with `current_assignee_id` only**. Unassigned sprint work **does not** count toward `assigned_md`.
2. **Sprint capacity** (`SprintCapacityService`): uses `sprint.capacity_md` vs **all** sprint tickets’ committed MD; drives `capacity_exceeded` and notifications.

**Overload labeling**

- API health: `red` / `overload_md` on team rows (`SprintTeamCapacityTest`).
- UI strings: `capacity.overloaded` / `capacity.overload` — **not** exact string “Over Capacity” from UAT script (`projects.json`).

**Gaps**

- **High:** UAT scenario “Capacity = 20, Assigned = 35” must set **`sprint.capacity_md`** for sprint-level alert; team allocations are separate. Easy to misconfigure in UI.
- **Medium:** Overload warnings on planning page depend on which hook/API the page calls; verify both indicators in live UI.

---

### Phase 4 — Work item

| Area | Create | Edit | Delete | Reorder | Complete | History |
|------|--------|------|--------|---------|----------|---------|
| Overview | Pass | Pass | Partial | N/A | Via status | Stub |
| Acceptance criteria | Pass | Pass | Pass | N/A | Checkbox | — |
| Dependencies | API only | API only | API only | — | — | Stub UI |
| Comments | — | — | — | — | — | **Stub** (count only) |
| Subtasks | — | — | — | — | — | **Stub** (empty message) |
| History | — | — | — | — | — | **Stub** (hint text only) |

**Evidence:** `WorkItemDetailPage.tsx` — dependencies/subtasks/comments/history panels are placeholders.

**Severity:** **Critical** for enterprise UAT claiming full work-item lifecycle.

---

### Phase 5 — Dependencies

| Check | Result |
|-------|--------|
| Create “B blocked by A” (FS) | **Pass (API)** — `POST /api/v1/tickets/{b}/dependencies` with `source_ticket_id: A` |
| Appears in API `blocked_by` | **Pass** |
| Backlog “blocked” metric | **Partial** — counts any ticket with incoming dependency, **without** checking if blocker is done |
| Board column “Blocked” | **Partial** — `SprintBoardColumnFilter::blockedTicketIds` marks **all** dependency targets; excludes them from other columns **even if blocker is complete** |
| Gantt blocked highlight | **Partial** — `kanban_column === 'blocked'` only, not FS dependency state |
| Complete B before A | **Allowed — WARNING** |

**Documented expected behavior (actual)**

- System **allows** completing/moving dependent work before blockers are done.
- API exposes `warnings` for open blockers (`TicketDependencyController`) but **no enforcement** on `ProjectBoardService::move`.
- **Severity: High** — misleading “blocked” counts and column placement; not true dependency-aware workflow.

---

### Phase 6 — Board

| Check | Result |
|-------|--------|
| Columns To Do / In Progress / QA / Done | **Partial** — fixed enum: Sprint To Do, In Progress, In Review, **QA/Testing**, Blocked, Done (`KanbanColumn`) |
| Custom column names per project | **No** — mapped to department ticket statuses at project creation |
| Status updates + audit | **Pass (logic)** | `ProjectBoardService::move` + `AuditService` |
| Realtime | **Partial** | `TicketMoved` event; requires Reverb/queue running |
| Sprint progress | **Partial** | Health on sprint resource; board itself does not show burndown |
| Persistence after reload | **Pass (logic)** | `ticket_status_id` persisted |

**Gap:** UAT asked for 4 columns; product ships **6** board columns including Blocked and In Review.

---

### Phase 7 — Capacity

| Formula | Implementation |
|---------|----------------|
| Capacity | Per-user `ProjectSprintCapacityAllocation.capacity_md` + sprint-level `capacity_md` |
| Assigned | Sum `ManDayResolver::ticketEstimateMd` for assigned tickets (team) or all sprint tickets (sprint summary) |
| Remaining | `capacity - assigned` (can be **negative**) |
| Utilization | `round(assigned/capacity*100)`; 100% if capacity 0 and assigned > 0 |
| Overloaded | `remaining_md < 0` → health `red`; workload service `status: overloaded` |

| Edge case | Result |
|-----------|--------|
| 0 capacity, 0 assigned | Utilization 0, health green (unless assigned > 0 → red) |
| 0 capacity, assigned > 0 | health **red** — tested |
| Negative remaining | Exposed as negative `remaining_md` and `overload_md` |
| Closed sprint | Allocations remain; no special “frozen” mode |
| Archived sprint | **No archive enum** — `CLOSED` + soft delete on sprints |

**Gap:** **Medium** — “Over Capacity” vs “Overloaded” / `red` inconsistency between sprint-level and team-level UIs.

---

### Phase 8 — Sprint health

| Scenario | Algorithm | UI explanation |
|----------|-----------|----------------|
| Healthy (≥80) | `SprintHealthEngineService` | `recommendation` text returned |
| Warning (60–79) | Score bands in `docs/SPRINT_HEALTH_ALGORITHM.md` | Partial on sprint cards |
| At risk (40–59) | Notifications for at_risk | `PROJECT_HEALTH_CRITICAL` type (overloaded enum use) |
| Critical (<40) | Same | Recommendation string |

**Bugs**

- **High:** Velocity trend penalty checks `'declining'` but `VelocityService::trend()` returns **`'down'`** — penalty **never applied** (`SprintHealthEngineService` line 58).
- **Medium:** `ProjectHealthUpdated` event is **imported but never dispatched** — no health-changed notification pipeline from project health recalculation.

---

### Phase 9 — Burndown

| Check | Result |
|-------|--------|
| Ideal line | Linear from total MD over date range — **real calculation** |
| Actual / remaining / completed MD | From `completed_at` + `ManDayResolver` — **not placeholder** |
| Empty sprint | `total_md = 0` → chart empty state (`BurndownChart` `hasData`) |
| Closed sprint | Uses historical `completed_at` — OK |
| Fake chart values | **Not detected** in frontend; empty when no MD |

**Caveat:** Burndown uses **completion timestamp**, not day-by-day partial progress — acceptable for MD burndown but not hour-logging granularity.

---

### Phase 10 — Velocity

| Check | Result |
|-------|--------|
| Uses completed MD | **Yes** — `VelocityService`, closed sprints only |
| Not ticket count | **Yes** |
| Average / best / worst / trend | **Yes** in API |
| 5 completed sprints | **Pass (logic)** — needs data seeding for demo |

**Bug:** Trend `down` vs health engine `declining` (above).

---

### Phase 11 — Gantt

| Check | Result |
|-------|--------|
| Hierarchy project → sprint → epic → work item | **Pass (API)** — `ProjectGanttService` |
| Month / quarter / year | **Pass (UI)** — `ProjectGanttPage`, `PortfolioTimelinePage` |
| Today marker | **Verify live** — not confirmed in static review |
| Blocked / overdue highlight | **Partial** — overdue supported; blocked mainly `kanban_column` |
| Sprint boundaries / capacity markers | **Partial** — capacity in summary stats, not full marker UX |

**UX:** `GanttTimeline` `min-width: 800px` — horizontal scroll on narrow viewports.

---

### Phase 12 — Risks

| CRUD + mitigation + status | **Pass (API)** — `ProjectRiskController` |
| Dashboard / project health impact | **Partial** — `ProjectHealthService::criticalRisksCount` exists; weight in health score limited |

---

### Phase 13 — Milestones

| Check | Result |
|-------|--------|
| Create + due tomorrow | **Pass (API)** |
| Reminders | **Pass (scheduled)** — `AgileNotificationMonitorService::monitorMilestones` |
| Overdue | **Pass** |
| Dashboard / Gantt | **Partial** — milestone rows in Gantt service |

---

### Phase 14 — Notifications

| Type | In-app | Email | Realtime | Dedupe |
|------|--------|-------|----------|--------|
| Sprint starts | Yes | Templates exist | Partial | `project_due_events` |
| Sprint ends | Uses `sprint_overdue` for “ends tomorrow” | Yes | Partial | Yes |
| Sprint closed | `NotifyOnSprintLifecycle` | Yes | Partial | — |
| Sprint overdue | Yes | Yes | Partial | Yes |
| Milestone due/overdue | Yes | Yes | Partial | Yes |
| Capacity exceeded | Yes + `SprintCapacityChanged` | Yes | Event listener | Hourly dedupe on live |
| Project overdue / critical | Yes | Yes | Partial | Yes |
| Work item overdue | Yes (monitor) | Yes | Partial | Yes |
| Health changed | **Missing dedicated type** | **No** | **No** | — |

**Gaps**

- **High:** No explicit **health_changed** notification; sprint at-risk reuses `project_health_critical`.
- **Medium:** Full matrix requires queue worker + mail + Reverb — not verified running.
- **Medium:** Email delivery not audited in this pass.

---

### Phase 15 — Roles & permissions

| Surface | Viewer | Member | Manager/Owner | Dept head | Company admin |
|---------|--------|--------|---------------|-----------|---------------|
| View project/agile pages | Expected yes | Yes | Yes | Yes | Yes |
| Mutate backlog/sprints | **API: deny** (needs `update` on project) | Yes | Yes | Yes | Yes |
| Capacity allocations | No | No | Yes | Yes | Yes |
| Direct URL | Policy on API; UI may still render routes | — | — | — | — |

**Gaps**

- **High:** **No automated tests** for `ProjectMemberRole::VIEWER` on agile endpoints (only ticketing viewer tests exist).
- **Medium:** UI route guard is `ROUTES.PROJECTS` — finer RBAC is API-side only; viewers might see action buttons until API returns 403.

---

### Phase 16 — Project closure

| Step | Result |
|------|--------|
| Complete all work | **Not enforced** |
| Close sprint | **Allowed with open tickets** — `ProjectSprintService::close` sets status only |
| Close project | Status → `completed` sets `completed_at` — **no validation** of open sprints/tickets |
| Archive | **Soft delete only** — no distinct archive workflow |
| Portfolio dashboard | **Implemented** — `PortfolioDashboardPage` / command center |
| Closure notifications | **Partial** |

**Severity:** **High** for enterprise closure governance.

---

### Phase 17 — UX

| Check | Result |
|-------|--------|
| Consistent agile layout components | **Good** — `projectsLayout`, `ProjectPageShell`, `ContentPanel` |
| Work item tabs | **Poor** — multiple tabs are stubs |
| CRUD-heavy work item page | **Medium** — inline edit on overview OK; other tabs empty |
| Design system alignment | **Partial** — prior `PROJECTS_UI_ALIGNMENT_REPORT.md` claims; not re-verified visually |
| Giant whitespace | **Not measured** — no live UI |

---

### Phase 18 — Responsive

| Breakpoint | Board | Backlog | Planning | Capacity | Gantt |
|------------|-------|---------|----------|----------|-------|
| 320–390 | **Risk** | **Risk** | Split pane stacks @960px | Unknown | **800px min width** |
| 768 | Likely OK | OK | OK | OK | Scroll |
| 1024+ | OK | OK | OK | OK | OK |

**Evidence:** `@media (max-width: 960px)` in `ResizableSplitPane`; Gantt `min-width: 800px`. **No audit at 320/375/390px** without browser run.

---

### Phase 19 — Data integrity

| Action | Behavior | Risk |
|--------|----------|------|
| Delete project | Soft delete; tickets `project_id` → **null** (`nullOnDelete`) | **Orphan department tickets** remain |
| Delete sprint | Tickets moved to backlog (`sprint_id` null) | OK |
| Delete epic | Verify FK — epic_id on tickets likely nullified | Medium |
| Delete work item | Ticket delete rules | OK |
| Metrics after delete | Stale sprint allocations may remain until cascade | Low |

**Severity:** **High** — deleting project does not delete work items; portfolio metrics may drift if orphans exist.

---

## Issue register

| ID | Severity | Phase | Finding | Remediation |
|----|----------|-------|---------|-------------|
| A-01 | **Critical** | 4 | Work item Dependencies/Comments/Subtasks/History UI are stubs | Implement tabs wired to ticket APIs (deps, messages, audit, subtasks) |
| A-02 | **High** | 5 | Dependency completion not enforced; false “blocked” board/backlog counts | Enforce on board move; only mark blocked when predecessor not `is_done` |
| A-03 | **High** | 16 | Sprint/project close without open-work validation | Add closure gates + user confirmation |
| A-04 | **High** | 8 | Velocity `down` vs `declining` — health penalty dead | Align enum/string in `SprintHealthEngineService` |
| A-05 | **High** | 14 | No health-changed notification; `ProjectHealthUpdated` unused | Dispatch event + `NotificationType` + listener |
| A-06 | **High** | 15 | No viewer RBAC tests on agile APIs; possible UI/API mismatch | Feature tests + hide actions for viewer |
| A-07 | **High** | 19 | Project delete orphans tickets (`nullOnDelete`) | Cascade archive/delete or block delete with open items |
| A-08 | **Medium** | 3 | Dual capacity models (team vs sprint MD) confuse overload | Single dashboard definition; document in UI |
| A-09 | **Medium** | 3 | Unassigned sprint work excluded from team `assigned_md` | Include unassigned in sprint load or warn |
| A-10 | **Medium** | 6 | Six fixed columns vs UAT four-column expectation | Mapping UI or doc; map Review/QA to dept statuses |
| A-11 | **Medium** | 1–20 | Live UAT + screenshots not executed | Run `php artisan serve`, `npm run dev`, execute scripted UAT |
| A-12 | **Medium** | 2 | Backlog “collaborators” not exposed | Integrate ticket collaborators into backlog cards |
| A-13 | **Low** | 3 | Label “Over Capacity” vs “Overloaded” | i18n alignment |
| A-14 | **Low** | 10 | Trend labels `up`/`down` vs executive copy `improving`/`declining` | Normalize API + UI |

---

## API spot-check reference

| Endpoint | Purpose |
|----------|---------|
| `POST /api/v1/projects` | Create project |
| `GET /api/v1/projects/{id}/backlog` | Backlog dashboard |
| `PATCH /api/v1/projects/{id}/backlog/reorder` | Order |
| `POST /api/v1/projects/{id}/sprints` | Sprint create |
| `PUT .../sprints/{id}/capacity-allocations` | Team MD capacity |
| `GET .../team-capacity` | Overload rows |
| `GET .../sprints/{id}/burndown` | Burndown series |
| `GET /api/v1/projects/{id}/velocity` | Velocity MD |
| `GET .../executive-health` | Sprint health |
| `PATCH .../sprints/{id}/close` | Close sprint (no validation) |
| `POST /api/v1/tickets/{id}/dependencies` | FS dependency |

---

## Scores (0–100)

| # | Category | Score | Rationale |
|---|----------|-------|-----------|
| 1 | Business Flow | **64** | Create/plan OK; closure & notifications incomplete |
| 2 | Scrum Compliance | **58** | Core Scrum artifacts present; enforcement & work-item depth weak |
| 3 | Capacity Planning | **68** | MD math sound; dual models + unassigned gap |
| 4 | Sprint Management | **62** | Start/close OK; no governance on close |
| 5 | Reporting | **70** | Burndown/velocity real; health bug dampens trust |
| 6 | Permission | **54** | Model exists; weak viewer validation & UI parity |
| 7 | UX | **50** | Shell good; work-item tabs undermine enterprise feel |
| 8 | Responsive | **46** | Limited breakpoints; Gantt/board mobile risk |
| 9 | Data Integrity | **58** | Soft deletes OK; project delete orphans tickets |
| 10 | **Production Readiness** | **47** | **NOT PRODUCTION READY** — lifecycle not fully validated end-to-end |

**Weighted overall (equal weights): 57.7 → rounded 58/100**

---

## Recommended remediation order

1. **A-01** — Complete work item detail surfaces (blocks Phase 4 sign-off).  
2. **A-02** — Dependency-aware blocking (blocks Phase 5 sign-off).  
3. **A-03, A-07** — Closure and delete integrity (blocks Phase 16/19).  
4. **A-04, A-05** — Health/velocity correctness + notifications (Phase 8/14).  
5. **A-06, A-11** — RBAC tests + full live UAT with screenshots on staging.

---

## Sign-off criteria (for “Production Ready”)

All must be true:

- [ ] Live UAT script Phases 1–20 executed on staging with evidence (screenshots + API logs).  
- [ ] Project → closure with permissions, notifications, audit, and portfolio verified.  
- [ ] Work item tabs fully functional (not stub).  
- [ ] Dependency rules documented **and** enforced (or explicitly accepted with warning UX).  
- [ ] Capacity overload visible and consistent (team + sprint) for 20/35 MD scenario.  
- [ ] Viewer cannot mutate via API or UI.  
- [ ] No orphan tickets after project delete (or delete blocked).  
- [ ] Queue, mail, and Reverb verified for notification matrix.

---

## References

- `docs/ENTERPRISE_AGILE_PLATFORM_V2_REPORT.md`
- `docs/SPRINT_HEALTH_ALGORITHM.md`
- `docs/NOTIFICATION_MATRIX.md`
- `docs/CAPACITY_PLANNING_DESIGN.md`
- `tests/Feature/Projects/` (41 tests passing at audit time)

---

*This document is an audit deliverable only. No production fixes were applied during this pass (audit-first per charter).*
