diff --git a/.github/workflows/docs-gate.yml b/.github/workflows/docs-gate.yml new file mode 100644 index 0000000..b47e377 --- /dev/null +++ b/.github/workflows/docs-gate.yml @@ -0,0 +1,129 @@ +name: Docs Gate + +# Enforce: +# 1. All documentation lives under docs/ only +# 2. Every PR must include at least one docs/ change + +on: + pull_request: + branches: [main] + +jobs: + # ------------------------------------------------------------------------- + # Job 1: Verify no .md files were added outside docs/ (except README.md) + # ------------------------------------------------------------------------- + no-docs-outside-docs-folder: + name: No Markdown outside docs/ + runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: read + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Detect .md files added outside docs/ + run: | + # List .md files changed/added in this PR that are NOT inside docs/ and NOT README.md + BASE_SHA="${{ github.event.pull_request.base.sha }}" + HEAD_SHA="${{ github.event.pull_request.head.sha }}" + + VIOLATIONS=$(git diff --name-only "$BASE_SHA" "$HEAD_SHA" \ + | grep '\.md$' \ + | grep -v '^docs/' \ + | grep -v '^README\.md$' \ + || true) + + if [ -n "$VIOLATIONS" ]; then + echo "❌ The following Markdown files were added or modified outside the docs/ folder:" + echo "$VIOLATIONS" + echo "" + echo "Rule: All project documentation MUST live under docs/" + echo " (README.md is the only allowed exception)" + echo "" + echo "Please move the files to docs/ and update any relative links." + exit 1 + fi + + echo "✅ No Markdown files found outside docs/ — rule satisfied." + + # ------------------------------------------------------------------------- + # Job 2: Require at least one docs/ file change per PR + # ------------------------------------------------------------------------- + docs-required-in-pr: + name: PR must include a docs/ change + runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: read + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Check for docs/ changes + run: | + BASE_SHA="${{ github.event.pull_request.base.sha }}" + HEAD_SHA="${{ github.event.pull_request.head.sha }}" + + DOCS_CHANGED=$(git diff --name-only "$BASE_SHA" "$HEAD_SHA" \ + | grep '^docs/' \ + || true) + + if [ -z "$DOCS_CHANGED" ]; then + echo "❌ This PR does not include any changes to the docs/ folder." + echo "" + echo "Rule: Every PR must document what changed." + echo "" + echo "Please update at least one of:" + echo " docs/developer-guide.md — for backend or infrastructure changes" + echo " docs/user-guide.md — for frontend feature changes" + echo " docs/TODO.md — for completed tasks or newly discovered items" + echo " docs/ai-instructions.md — for convention changes" + echo " docs/investigation-report.md — for architectural findings" + echo "" + echo "See docs/ai-instructions.md for the full pre-PR checklist." + exit 1 + fi + + echo "✅ docs/ changes found in this PR:" + echo "$DOCS_CHANGED" + + # ------------------------------------------------------------------------- + # Job 3: Verify all files referenced in docs/ actually exist + # ------------------------------------------------------------------------- + docs-links-valid: + name: Validate docs/ internal links + runs-on: ubuntu-latest + permissions: + contents: read + + steps: + - uses: actions/checkout@v4 + + - name: Check that docs/ index files exist + run: | + REQUIRED_FILES=( + "docs/developer-guide.md" + "docs/user-guide.md" + "docs/TODO.md" + "docs/ai-instructions.md" + ) + + MISSING=() + for f in "${REQUIRED_FILES[@]}"; do + if [ ! -f "$f" ]; then + MISSING+=("$f") + fi + done + + if [ ${#MISSING[@]} -gt 0 ]; then + echo "❌ Required documentation files are missing:" + printf ' %s\n' "${MISSING[@]}" + exit 1 + fi + + echo "✅ All required docs/ files exist." diff --git a/.gitignore b/.gitignore index 8668165..431f69c 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,4 @@ frontend/.vite/ # OS .DS_Store Thumbs.db +nohup.out diff --git a/docs/TODO.md b/docs/TODO.md new file mode 100644 index 0000000..bd4f954 --- /dev/null +++ b/docs/TODO.md @@ -0,0 +1,183 @@ +# netAI — TODO List + +> Last updated: 2026-04-06 | Generated from investigation report + +Priority levels: 🔴 Critical · 🟠 High · 🟡 Medium · 🟢 Low + +--- + +## 🔴 Critical + +- [ ] **AUTH-001** — Add JWT authentication (`POST /api/auth/login` returning signed token) + - Files: `backend/app/main.py`, new `backend/app/api/routes/auth.py`, new `backend/app/core/auth.py` + - Dependencies: `python-jose`, `passlib` already in `requirements.txt` + - Accept: All protected routes return 401 without valid Bearer token + +- [ ] **AUTH-002** — Add `Depends(get_current_user)` to all write endpoints (POST, PUT, DELETE) + - Files: all `backend/app/api/routes/*.py` + - Note: GET endpoints may remain public for initial read-only dashboard use + +- [ ] **AUTH-003** — Upgrade `python-jose` from `3.4.0` to latest (or migrate to `PyJWT>=2.8`) + - Files: `backend/requirements.txt` + - Reason: Known CVEs in python-jose 3.4.0 (GHSA-* — check GitHub Advisory DB) + +- [ ] **AUTH-004** — Add authentication to WebSocket endpoint `/ws` + - Files: `backend/app/main.py` + - Pattern: `?token=` query param validated on connect + +- [ ] **DB-001** — Replace in-memory datastore with persistent SQLite (dev) / PostgreSQL (prod) + - Files: `backend/app/core/database.py` → SQLAlchemy + Alembic migrations + - Impact: All data currently lost on every restart + +--- + +## 🟠 High + +- [ ] **TEST-001** — Add unit tests for all backend services + - Directory: `backend/tests/` + - Files: `test_device_service.py`, `test_threat_service.py`, `test_config_service.py`, `test_nlp_service.py`, `test_anomaly_detector.py` + - Tools: `pytest`, `pytest-asyncio`, `httpx.AsyncClient` + +- [ ] **TEST-002** — Add FastAPI integration tests using `TestClient` + - File: `backend/tests/test_routes.py` + - Goal: ≥ 80% endpoint coverage + +- [ ] **TEST-003** — Add frontend unit tests + - Tools: `vitest` + `@testing-library/react` + - Priority pages: Dashboard, Devices, Threats, Alerts + +- [ ] **API-001** — Add pagination to all list endpoints + - Pattern: `?skip=0&limit=50` query params on GET endpoints returning arrays + - Files: `devices.py`, `alerts.py`, `threats.py`, `links.py`, `bgp.py`, `circuits.py`, `workflows.py` + +- [ ] **FRONTEND-001** — Remove hardcoded `DEVICE_ID_MAP` from `Config.tsx` + - Current: `Config.tsx` has a static map of hostname → device_id + - Fix: Fetch device list from `GET /api/devices` and build map dynamically + +- [ ] **CORS-001** — Restrict CORS `allow_origins` from `["*"]` to frontend domain in production + - File: `backend/app/main.py:121` + - Pattern: Read from environment variable `ALLOWED_ORIGINS` + +- [ ] **CI-001** — Add ESLint step to frontend CI job + - File: `.github/workflows/ci.yml` + - Command: `npm run lint` + +- [ ] **CI-002** — Add `pytest` step to backend CI job + - File: `.github/workflows/ci.yml` + - Prerequisite: TEST-001 must be done first + +--- + +## 🟡 Medium + +- [ ] **NLP-001** — Integrate a real LLM for natural language processing + - Options: OpenAI API, Azure OpenAI, local Ollama (llama3) + - File: `backend/app/services/nlp_service.py` + - Current: Keyword/intent matching only (~15 intents) + +- [ ] **NLP-002** — Add conversation history / context to NLP service + - Allow multi-turn conversations with session tracking + +- [ ] **NLP-003** — Add streaming response support for NLP endpoint + - Use FastAPI `StreamingResponse` with SSE or WebSocket + +- [ ] **FRONTEND-002** — Add React error boundary component + - Wrap page routes in an `` that shows a friendly fallback UI + - File: `frontend/src/components/ErrorBoundary.tsx` + +- [ ] **FRONTEND-003** — Decompose large page components into sub-components + - Pages > 300 lines: `Dashboard.tsx`, `Devices.tsx`, `Threats.tsx`, `DeviceDetail.tsx` + - Extract chart sections, table sections into separate files under `components/` + +- [ ] **FRONTEND-004** — Add route-level `404` page + - Currently: `*` route redirects to Dashboard, which masks bad URLs + +- [ ] **API-002** — Add `DELETE /api/alerts/{id}` endpoint + - File: `backend/app/api/routes/alerts.py` + +- [ ] **API-003** — Add `PUT /api/devices/{id}` endpoint for device metadata updates + - File: `backend/app/api/routes/devices.py` + +- [ ] **API-004** — Add `GET /api/devices?search=&type=&status=` filtering + - File: `backend/app/api/routes/devices.py` + +- [ ] **API-005** — Add `GET /api/audit-log` endpoint for config change history + - File: `backend/app/api/routes/config_mgmt.py` or new `audit.py` + +- [ ] **MONITORING-001** — Add Prometheus metrics endpoint to backend + - Package: `prometheus-fastapi-instrumentator` + - Expose at `/metrics` + +- [ ] **BGP-001** — Wire BGP sessions to real or simulated real-time data source + - Currently: 100% static mock data in `bgp.py` + +- [ ] **CIRCUIT-001** — Wire circuit status to real or simulated real-time data + - Currently: 100% static mock data in `circuits.py` + +- [ ] **WORKFLOW-001** — Implement actual workflow execution engine + - Currently: Workflows return mock "running" status + - Could use `asyncio.create_task()` with progress broadcasting via WebSocket + +--- + +## 🟢 Low + +- [ ] **A11Y-001** — Add ARIA labels to all interactive elements in frontend + - Files: all `frontend/src/pages/*.tsx` and `components/*.tsx` + - Tools: `axe-core` or `@axe-core/react` for automated checks + +- [ ] **A11Y-002** — Add keyboard navigation support to Topology SVG map + - File: `frontend/src/pages/Topology.tsx` + +- [ ] **BUNDLE-001** — Add Vite bundle analyser to measure frontend bundle size + - Package: `rollup-plugin-visualizer` + - Goal: Identify heavy dependencies (Recharts is ~300 KB gzip) + +- [ ] **DOCS-001** — Add inline JSDoc / TSDoc comments to all TypeScript interfaces + - File: `frontend/src/types/index.ts` + +- [ ] **DOCS-002** — Add OpenAPI tags, descriptions, and response schemas to all routes + - All `backend/app/api/routes/*.py` + - Goal: Make `/docs` Swagger UI more useful + +- [ ] **DOCS-003** — Add architecture decision records (ADRs) under `docs/adr/` + - Template: `docs/adr/0001-in-memory-datastore.md`, `0002-nlp-keyword-matching.md` + +- [ ] **INFRA-001** — Add Kubernetes deployment manifests + - Directory: `k8s/` + - Files: `backend-deployment.yaml`, `frontend-deployment.yaml`, `ingress.yaml` + +- [ ] **INFRA-002** — Add TLS configuration example to `nginx.conf` + - Show Let's Encrypt / Certbot integration + +- [ ] **INFRA-003** — Add `VITE_API_BASE_URL` environment variable support + - Current: Uses relative paths; hard to point at a remote backend + +- [ ] **SECURITY-001** — Add rate limiting to NLP and auth endpoints + - Package: `slowapi` (FastAPI wrapper for `limits`) + - Limit: 30 req/min per IP on `/api/nlp/query` + +- [ ] **SECURITY-002** — Add request size limit to prevent DoS via large payloads + - FastAPI `Request` body size limit middleware + +- [ ] **SECURITY-003** — Add Content Security Policy headers in nginx + - File: `frontend/nginx.conf` + +--- + +## Completed ✅ + +- [x] 15-page React frontend with all pages wired to backend +- [x] 57-endpoint FastAPI backend across 14 modules +- [x] WebSocket real-time telemetry broadcast +- [x] ML anomaly detector (z-score, IQR, EWMA) +- [x] Multi-vendor adapter system (8 vendors) +- [x] Docker Compose with health checks +- [x] GitHub Actions CI (backend + frontend + docker validate) +- [x] Developer guide and user guide in `docs/` +- [x] Field mapping adapters (backend ↔ frontend naming) +- [x] Pydantic v2 data validation throughout backend + +--- + +*See [investigation-report.md](investigation-report.md) for full analysis. See [ai-instructions.md](ai-instructions.md) for AI contribution guidelines.* diff --git a/docs/ai-instructions.md b/docs/ai-instructions.md new file mode 100644 index 0000000..4e3b5d1 --- /dev/null +++ b/docs/ai-instructions.md @@ -0,0 +1,491 @@ +# netAI — AI Agent Instructions + +> These instructions govern how AI coding agents (GitHub Copilot, Claude, GPT-4, Cursor, etc.) +> must behave when contributing to the **netAI** repository. +> +> **All contributors — human and AI — must follow these rules.** + +--- + +## Table of Contents + +1. [Golden Rules](#golden-rules) +2. [Documentation Requirements](#documentation-requirements) +3. [Project Conventions](#project-conventions) +4. [Backend Guidelines](#backend-guidelines) +5. [Frontend Guidelines](#frontend-guidelines) +6. [Testing Requirements](#testing-requirements) +7. [Security Rules](#security-rules) +8. [Commit & PR Rules](#commit--pr-rules) +9. [Prohibited Actions](#prohibited-actions) +10. [Quick Reference](#quick-reference) + +--- + +## Golden Rules + +1. **Documentation first** — You MUST update or create documentation in `docs/` before committing code or opening a PR. +2. **docs/ folder only** — All project documentation lives exclusively in the `docs/` folder. Never create `.md` files outside `docs/` (except `README.md`). +3. **Small, surgical changes** — Change only what is necessary. Do not refactor unrelated code. +4. **Never break existing behavior** — Run the CI checks before finalising. Do not remove or modify existing tests. +5. **No secrets** — Never commit credentials, API keys, passwords, or tokens. Use environment variables. +6. **Security first** — Never introduce authentication bypasses, CORS wildcards without justification, or unvalidated user input. + +--- + +## Documentation Requirements + +### Before Every Commit + +You MUST ensure the following are current before committing: + +- [ ] If you added or changed a **backend route** → update `docs/developer-guide.md` (API Reference section) +- [ ] If you added or changed a **frontend page** → update `docs/developer-guide.md` (Frontend section) and `docs/user-guide.md` +- [ ] If you added a **new vendor** → update both `docs/developer-guide.md` (Multi-Vendor section) and `docs/user-guide.md` (Supported Vendors section) +- [ ] If you changed the **data model** → update the field mapping table in `docs/developer-guide.md` +- [ ] If you changed **environment variables** → update the environment variable tables in both guides +- [ ] If you found and fixed a **bug** → add the fix to `docs/TODO.md` (Completed section) +- [ ] If you completed a **TODO item** → move it to the Completed section in `docs/TODO.md` + +### Before Opening a PR + +You MUST create or update **at minimum one** of these docs files to describe your change: + +| docs/ file | When to update | +|-----------|----------------| +| `developer-guide.md` | Any backend or infrastructure change | +| `user-guide.md` | Any frontend feature visible to users | +| `TODO.md` | Any completed task or new task discovered | +| `investigation-report.md` | Major architectural findings or security discoveries | +| `ai-instructions.md` | Changes to AI contribution conventions | + +### How to Document + +- Write documentation in **Markdown**, following the style of existing docs files. +- Use **present tense** ("Returns a list of devices", not "will return"). +- Include **code examples** for any new API endpoint or configuration. +- Update the **Table of Contents** of the modified document. +- Add a **date stamp** to changed sections when appropriate. + +--- + +## Project Conventions + +### File Locations + +| Content type | Location | Rule | +|-------------|----------|------| +| All documentation | `docs/` | MANDATORY — no exceptions | +| Backend route modules | `backend/app/api/routes/` | One file per domain | +| Backend services | `backend/app/services/` | Business logic only | +| Backend models | `backend/app/core/models.py` | Pydantic v2 | +| Frontend pages | `frontend/src/pages/` | One file per page | +| Frontend components | `frontend/src/components/` | Shared components only | +| TypeScript types | `frontend/src/types/index.ts` | All interfaces here | +| Temporary scripts | `/tmp/` | Never commit temp files | + +### Naming Conventions + +| Context | Convention | Example | +|---------|-----------|---------| +| Python files | `snake_case` | `device_service.py` | +| Python functions | `snake_case` | `get_device_health()` | +| Python classes | `PascalCase` | `DeviceHealth` | +| TypeScript files | `PascalCase` | `DeviceDetail.tsx` | +| TypeScript interfaces | `PascalCase` | `Device`, `NetworkLink` | +| TypeScript functions | `camelCase` | `fetchDevices()` | +| React components | `PascalCase` | `MetricCard` | +| CSS classes | `kebab-case` | `metric-card` | +| Git branches | `copilot/` | `copilot/add-auth` | + +### API Design Rules + +- All API routes use `/api/...` prefix — **never** `/v1/...` +- GET endpoints must be idempotent +- POST endpoints return the created/modified resource +- Use Pydantic models for all request/response bodies — no raw `dict` +- Use `HTTPException` with appropriate status codes (404, 422, 500) +- All enumerations use `str, Enum` for JSON compatibility +- Field percentage values must use `Field(ge=0, le=100)` validator + +--- + +## Backend Guidelines + +### Adding a New Route + +```python +# 1. Create backend/app/api/routes/my_module.py +from fastapi import APIRouter, HTTPException +from app.core import database as db + +router = APIRouter(prefix="/api/my_module", tags=["my_module"]) + +@router.get("", response_model=list[MyModel]) +async def list_items(): + """Return all items.""" + return db.my_items_db + +@router.get("/{item_id}", response_model=MyModel) +async def get_item(item_id: str): + """Return a specific item by ID.""" + item = next((i for i in db.my_items_db if i.id == item_id), None) + if not item: + raise HTTPException(status_code=404, detail="Item not found") + return item +``` + +```python +# 2. Mount in backend/app/main.py +from app.api.routes import my_module +app.include_router(my_module.router) +``` + +```markdown +# 3. Document in docs/developer-guide.md → API Reference table +| GET | `/api/my_module` | List all items | +| GET | `/api/my_module/{id}` | Get item detail | +``` + +### Adding a Pydantic Model + +Always use Pydantic **v2** syntax: + +```python +# In backend/app/core/models.py +from pydantic import BaseModel, Field +from typing import Optional + +class MyModel(BaseModel): + id: str + name: str + value: Optional[float] = None + percentage: float = Field(ge=0, le=100) # Always validate range +``` + +Mirror the model in `frontend/src/types/index.ts` and note any field name differences in `docs/developer-guide.md`. + +### Service Layer Pattern + +```python +# In backend/app/services/my_service.py +from app.core import database as db +from app.core.models import MyModel + +def get_summary() -> dict: + """Compute summary statistics.""" + return { + "total": len(db.my_items_db), + "active": sum(1 for i in db.my_items_db if i.active), + } +``` + +Services must **not** import from `routes/` (no circular imports). Routes call services; services call `database` and `models`. + +### WebSocket Guidelines + +- The single `/ws` endpoint broadcasts telemetry every 5 seconds +- Do not add new WebSocket endpoints without documenting the message schema in `docs/developer-guide.md` +- All WebSocket messages must include `"event"` and `"timestamp"` fields + +### Anomaly Detection + +Use the existing detectors before adding new ML logic: + +```python +from app.core.ml.anomaly_detector import detect_cpu_anomaly, detect_traffic_anomaly + +result = detect_cpu_anomaly(current=92.3, history=[...]) +if result.is_anomaly: + # trigger alert +``` + +--- + +## Frontend Guidelines + +### Adding a New Page + +```tsx +// 1. Create frontend/src/pages/MyPage.tsx +import React, { useState, useEffect } from 'react' +import apiClient from '../api/client' +import LoadingSpinner from '../components/LoadingSpinner' + +const MyPage: React.FC = () => { + const [data, setData] = useState([]) + const [loading, setLoading] = useState(true) + + useEffect(() => { + apiClient.get('/api/my_module') + .then(r => setData(r.data)) + .catch(console.error) + .finally(() => setLoading(false)) + }, []) + + if (loading) return + return
{/* page content */}
+} + +export default MyPage +``` + +```tsx +// 2. Add lazy import and route in frontend/src/App.tsx +const MyPage = lazy(() => import('./pages/MyPage')) +// inside : +} /> +``` + +```tsx +// 3. Add navigation link in frontend/src/components/Sidebar.tsx +// Follow the existing pattern with Lucide icon + label +``` + +```markdown +// 4. Document in docs/user-guide.md and docs/developer-guide.md +``` + +### TypeScript Interface Rules + +All interfaces go in `frontend/src/types/index.ts`. Mirror backend Pydantic models. Note field name differences: + +```typescript +// In frontend/src/types/index.ts +export interface MyModel { + id: string + name: string // backend: name + value?: number + percentage: number // always 0–100 +} +``` + +### API Client + +Always use the shared Axios client: + +```typescript +import apiClient from '../api/client' + +// Good +const res = await apiClient.get('/api/devices') + +// Bad — never hardcode the base URL in pages +const res = await axios.get('http://localhost:8000/api/devices') +``` + +### Field Mapping Adapters + +When backend field names differ from frontend interfaces, write an explicit adapter function: + +```typescript +const adaptDevice = (raw: any): Device => ({ + hostname: raw.name, // backend: name → frontend: hostname + ip_address: raw.ip, // backend: ip → frontend: ip_address + device_type: raw.type, + os_version: raw.firmware_version, +}) + +const devices = rawData.map(adaptDevice) +``` + +Document the mapping in `docs/developer-guide.md`. + +--- + +## Testing Requirements + +### Backend Tests + +All new backend services and routes must have corresponding tests: + +```python +# backend/tests/test_my_module.py +from fastapi.testclient import TestClient +from app.main import app + +client = TestClient(app) + +def test_list_items(): + response = client.get("/api/my_module") + assert response.status_code == 200 + assert isinstance(response.json(), list) + +def test_item_not_found(): + response = client.get("/api/my_module/does-not-exist") + assert response.status_code == 404 +``` + +Run tests before committing: + +```bash +cd backend +pip install pytest pytest-asyncio httpx +pytest tests/ -v +``` + +### Frontend Tests + +New components and utility functions should have tests: + +```typescript +// frontend/src/components/__tests__/MetricCard.test.tsx +import { render, screen } from '@testing-library/react' +import MetricCard from '../MetricCard' + +test('renders metric card with title and value', () => { + render() + expect(screen.getByText('CPU')).toBeInTheDocument() + expect(screen.getByText('72%')).toBeInTheDocument() +}) +``` + +--- + +## Security Rules + +1. **Never hardcode credentials** in any file. Use environment variables. +2. **Never add `allow_origins=["*"]` in production** without a comment explaining why it is safe. +3. **Always validate input** — use Pydantic `Field` validators for numbers, `max_length` for strings. +4. **Never log sensitive data** (passwords, tokens, full config blobs). +5. **Check for injection** — all config text goes through `detect_config_anomaly()` before display. +6. **Upgrade vulnerable dependencies** — check GitHub Advisory Database before adding or upgrading packages. +7. **NLP queries must have max length** — add `max_length=2000` to `NLPQuery.query` field. +8. **Rate limit write endpoints** — use `slowapi` for POST/PUT/DELETE. + +--- + +## Commit & PR Rules + +### Commit Message Format + +``` +(): + +Types: feat | fix | docs | test | refactor | security | ci | chore +Scopes: backend | frontend | docs | ci | infra | auth | nlp | topology | ... + +Examples: + feat(backend): add JWT authentication middleware + fix(frontend): remove hardcoded DEVICE_ID_MAP in Config.tsx + docs(developer-guide): add new auth endpoints to API reference + test(backend): add unit tests for device_service + security(deps): upgrade python-jose to 3.5.0 +``` + +### Pre-Commit Checklist + +Before every commit, verify: + +- [ ] Documentation in `docs/` is updated (see [Documentation Requirements](#documentation-requirements)) +- [ ] No secrets or credentials in changed files +- [ ] All changed Python code imports successfully (`python -c "from app.main import app"`) +- [ ] Frontend builds without errors (`npm run build`) +- [ ] New backend routes are mounted in `main.py` +- [ ] New TypeScript interfaces are in `frontend/src/types/index.ts` +- [ ] `docs/TODO.md` updated if tasks were completed + +### Before Opening a PR + +- [ ] At least one `docs/` file is modified in the PR +- [ ] CI is green (or you have explained why a failure is pre-existing) +- [ ] PR description includes: what changed, why it changed, what was documented +- [ ] If auth-related: security review requested +- [ ] If DB-related: migration scripts included + +### PR Description Template + +```markdown +## What changed +Brief description of the code change. + +## Why +Link to TODO item, issue, or explanation. + +## Documentation updated +- [ ] docs/developer-guide.md — section updated:
+- [ ] docs/user-guide.md — section updated:
+- [ ] docs/TODO.md — tasks completed: + +## Testing +- [ ] Backend: `pytest tests/ -v` — all pass +- [ ] Frontend: `npm run build` — no errors +- [ ] Manual: tested locally with Docker Compose + +## Security considerations +None / +``` + +--- + +## Prohibited Actions + +The following actions are **PROHIBITED** for all AI agents and human contributors: + +| Action | Reason | +|--------|--------| +| Create `.md` files outside `docs/` (except `README.md`) | Docs must be centralised | +| Commit any file containing passwords, API keys, or tokens | Security policy | +| Remove existing tests | Could hide regressions | +| Add `eval()`, `exec()`, or dynamic code execution | Security risk | +| Add `shell=True` to subprocess calls | Command injection risk | +| Set `allow_credentials=True` with `allow_origins=["*"]` | CORS security vulnerability | +| Skip Pydantic validation with `model.model_construct()` | Bypasses security validators | +| Log request bodies containing user input at INFO level or below | Privacy / log injection | +| Add npm packages with known HIGH/CRITICAL CVEs | Supply chain security | +| Hard-code device IDs, IPs, or credentials in frontend code | Breaks multi-instance deployments | + +--- + +## Quick Reference + +### Run the full stack locally + +```bash +# Backend +cd backend && pip install -r requirements.txt +uvicorn app.main:app --reload --port 8000 + +# Frontend (new terminal) +cd frontend && npm install && npm run dev +``` + +### CI checks + +```bash +# Backend import check +cd backend && python -c "from app.main import app; print('OK')" + +# Frontend build +cd frontend && npm run build + +# Docker validation +docker compose config --quiet +``` + +### Key file locations + +| What | Where | +|------|-------| +| All documentation | `docs/` | +| Backend entry point | `backend/app/main.py` | +| Backend models | `backend/app/core/models.py` | +| In-memory DB | `backend/app/core/database.py` | +| ML detector | `backend/app/core/ml/anomaly_detector.py` | +| Vendor profiles | `backend/app/core/vendors.py` | +| Frontend routes | `frontend/src/App.tsx` | +| TypeScript types | `frontend/src/types/index.ts` | +| Axios client | `frontend/src/api/client.ts` | +| CI workflows | `.github/workflows/` | + +### API base path + +`/api/...` — Never `/v1/...` + +### WebSocket path + +`/ws` — Single endpoint for all real-time telemetry + +--- + +*These instructions are enforced by the `docs-gate.yml` CI workflow. PRs that do not include a `docs/` change will be flagged.* diff --git a/docs/investigation-report.md b/docs/investigation-report.md new file mode 100644 index 0000000..4b0f3a5 --- /dev/null +++ b/docs/investigation-report.md @@ -0,0 +1,397 @@ +# netAI — Full Project Investigation Report + +> Generated: 2026-04-06 | Branch: `copilot/investigate-project-for-report` + +--- + +## Table of Contents + +1. [Executive Summary](#executive-summary) +2. [Project Structure](#project-structure) +3. [Backend Analysis](#backend-analysis) +4. [Frontend Analysis](#frontend-analysis) +5. [Infrastructure & CI/CD](#infrastructure--cicd) +6. [Security Assessment](#security-assessment) +7. [Data Model Review](#data-model-review) +8. [API Completeness](#api-completeness) +9. [Code Quality](#code-quality) +10. [Gaps & Risks](#gaps--risks) +11. [Recommendations](#recommendations) + +--- + +## Executive Summary + +**netAI** is an enterprise-grade, AI-powered network monitoring and management platform. The full-stack application consists of a Python/FastAPI backend (57 REST endpoints + WebSocket) and a React 18/TypeScript frontend (15 pages). The system is containerised with Docker Compose and includes a GitHub Actions CI pipeline. + +| Metric | Value | +|--------|-------| +| Backend routes | 57 across 14 modules | +| Frontend pages | 15 (all lazy-loaded) | +| Supported vendors | 8 (Cisco, MikroTik, Juniper, Nokia, Linux, BDcom, VSOL, DBC) | +| Test coverage | ⚠️ 0 unit tests (integration-only) | +| Authentication | ❌ None | +| Persistent storage | ❌ In-memory only | +| Documentation | ✅ Developer + User guides in `docs/` | + +**Overall health: GOOD for a prototype / demonstration system. NOT production-ready without addressing security and persistence gaps.** + +--- + +## Project Structure + +``` +netAI/ +├── backend/ # Python 3.11 / FastAPI / Pydantic v2 +│ ├── app/ +│ │ ├── main.py # App entry, CORS, WebSocket, KPI, health +│ │ ├── api/routes/ # 14 route modules (57 endpoints) +│ │ ├── core/ +│ │ │ ├── database.py # In-memory datastore (15 seed devices) +│ │ │ ├── device_registry.py # Per-vendor capability matrix +│ │ │ ├── ml/ +│ │ │ │ └── anomaly_detector.py # z-score, IQR, EWMA +│ │ │ ├── models.py # Pydantic v2 models +│ │ │ └── vendors.py # Vendor profiles & fingerprint +│ │ └── services/ # Business logic (6 services) +│ ├── requirements.txt # 11 direct Python dependencies +│ └── Dockerfile +├── frontend/ # React 18 + TypeScript + Vite 8 +│ ├── src/ +│ │ ├── api/client.ts # Axios client (same-origin URL) +│ │ ├── components/ # 5 shared components +│ │ ├── pages/ # 15 page components +│ │ └── types/index.ts # TypeScript interfaces +│ ├── package.json # 7 prod deps + 4 dev deps +│ └── Dockerfile + nginx.conf +├── docs/ +│ ├── developer-guide.md +│ ├── user-guide.md +│ ├── investigation-report.md (this file) +│ ├── TODO.md +│ └── ai-instructions.md +├── docker-compose.yml # 2 services, healthcheck, network +└── .github/workflows/ + ├── ci.yml # 3 CI jobs + └── docs-gate.yml # Documentation enforcement +``` + +--- + +## Backend Analysis + +### Framework & Dependencies + +| Package | Version | Notes | +|---------|---------|-------| +| FastAPI | 0.115.14 | ✅ Latest stable | +| Uvicorn | 0.24.0 | ✅ Standard ASGI server | +| Pydantic | 2.4.2 | ✅ v2 syntax throughout | +| NumPy | 1.26.2 | ✅ Used for ML calculations | +| SciPy | 1.11.4 | ✅ Statistical analysis | +| python-jose | 3.4.0 | ⚠️ JWT ready but unused; has known CVEs — upgrade needed | +| passlib[bcrypt] | 1.7.4 | ⚠️ Auth ready but unused | +| websockets | 12.0 | ✅ Real-time telemetry | +| httpx | 0.25.1 | ✅ Async HTTP client | + +### Route Modules + +| Module | Endpoints | Data Source | Notes | +|--------|-----------|-------------|-------| +| topology | 2 | In-memory + topology_service | ✅ Working | +| threats | 3 | In-memory + threat_service | ✅ Working | +| config_mgmt | 6 | In-memory + config_service | ✅ Working | +| devices | 6 | In-memory + device_service | ✅ Working | +| software | 4 | In-memory + software_service | ✅ Working; 404 on unknown device_id | +| alerts | 2 | In-memory | ✅ Working | +| nlp | 1 | nlp_service (intent matching) | ✅ Working; no LLM integration | +| vendors | 3 | Static vendor profiles | ✅ Working | +| links | 3 | Synthetic data | ✅ Working | +| bgp | 3 | Static mock data | ⚠️ 100% mock | +| circuits | 2 | Static mock data | ⚠️ 100% mock | +| workflows | 3 | Static mock data | ⚠️ 100% mock | +| ip_management | 3 | Static mock data | ⚠️ 100% mock | +| reports | 4 | Computed from in-memory DB | ✅ Working | +| dashboard/kpi | 1 | Computed from in-memory DB | ✅ Working | + +### Core Services + +- **topology_service.py** — Builds topology graph, computes device positions, runs discovery simulation +- **threat_service.py** — Generates threat statistics, applies mitigations +- **config_service.py** — Config CRUD, compliance audit using anomaly_detector, rollback +- **device_service.py** — Device health, metrics time-series, failure prediction +- **software_service.py** — Firmware inventory, upgrade scheduling with device_id validation +- **nlp_service.py** — Keyword/intent matching for ChatOps; no LLM dependency + +### ML / Anomaly Detection + +Three algorithms implemented in `anomaly_detector.py`: +- **Z-Score** — CPU/memory spike detection, configurable threshold (default: 2.5σ) +- **IQR** — Traffic outlier detection, resistant to extremes +- **EWMA** — Trend-based drift detection with α=0.3 + +Additional: `predict_failure_probability()` — combines CPU trend, memory pressure, and uptime into a risk score. + +**Finding**: The anomaly detector is well-implemented but only used in `device_service.py` and `config_service.py`. It is not wired to real-time data streams. + +### WebSocket + +- Endpoint: `/ws` +- Broadcasts telemetry every 5 seconds with random Gaussian noise applied to CPU/memory +- Supports ping/pong echo +- No authentication on WebSocket connection + +--- + +## Frontend Analysis + +### Technology Stack + +| Technology | Version | Usage | +|-----------|---------|-------| +| React | 18.2 | UI framework | +| TypeScript | 5.3 | Type safety | +| Vite | 8.0 | Build tool + dev server proxy | +| React Router | 6.20 | SPA routing (15 routes) | +| Recharts | 2.10 | Charts and graphs | +| Axios | 1.6 | HTTP client | +| Lucide React | 0.294 | Icon library | + +### Pages Inventory + +| Page | Route | API Calls | Charts | Issues | +|------|-------|-----------|--------|--------| +| Dashboard | `/` | `/api/dashboard/kpi`, `/api/alerts` | BarChart, donut | ✅ | +| Topology | `/topology` | `/api/topology` | SVG custom | ✅ | +| Threats | `/threats` | `/api/threats` | PieChart, LineChart | ✅ | +| Config | `/config` | `/api/config/{id}` | — | ⚠️ Hardcoded DEVICE_ID_MAP | +| Devices | `/devices` | `/api/devices`, `/api/devices/{id}/health` | LineChart | ✅ | +| Software | `/software` | `/api/software/inventory`, `/api/software/upgrade` | — | ✅ | +| Alerts | `/alerts` | `/api/alerts` | BarChart | ✅ | +| NLP | `/nlp` | `/api/nlp/query` | — | ✅ | +| LinkMonitor | `/links` | `/api/links`, `/api/links/stats` | — | ✅ | +| BGP | `/bgp` | `/api/bgp/sessions`, `/api/bgp/hijacks` | — | ✅ | +| Circuits | `/circuits` | `/api/circuits` | — | ✅ | +| Workflows | `/workflows` | `/api/workflows`, `/api/workflows/runs` | — | ✅ | +| IPManagement | `/ip-management` | `/api/ip/subnets`, `/api/ip/assignments` | — | ✅ | +| Reports | `/reports` | `/api/reports/*` | BarChart, LineChart | ✅ | +| DeviceDetail | `/devices/:id` | `/api/devices/{id}`, `/api/devices/{id}/health` | LineChart | ✅ | + +### Field Mapping Adapters + +Backend uses Python naming conventions, frontend uses web conventions. Adapters exist in several pages: + +| Page | Backend Field | Frontend Field | +|------|---------------|----------------| +| Devices.tsx | `name` | `hostname` | +| Devices.tsx | `ip` | `ip_address` | +| Devices.tsx | `type` | `device_type` | +| Devices.tsx | `firmware_version` | `os_version` | +| Topology.tsx | `devices` | `nodes` | +| Topology.tsx | `timestamp` | `last_updated` | +| Alerts.tsx | `device_name` | `device_hostname` | +| Threats.tsx | `target_ip` | `destination_ip` | + +### Component Library + +- `Header.tsx` — Top bar with WebSocket live/offline indicator +- `Sidebar.tsx` — Navigation with all 14 page links + icons +- `MetricCard.tsx` — KPI card with title, value, and trend +- `StatusBadge.tsx` — Coloured severity/status badge +- `LoadingSpinner.tsx` — Suspense fallback + +--- + +## Infrastructure & CI/CD + +### Docker Compose + +- 2 services: `backend` (port 8000) and `frontend` (port 3000→80) +- Backend has health check: polls `/health` every 30s +- Frontend depends on backend being healthy +- No volume mounts (in-memory data is ephemeral) +- No secrets management + +### nginx Configuration + +- Proxies `/api/` → `http://backend:8000` +- Proxies `/ws` → `ws://backend:8000` with WebSocket upgrade headers +- Serves React SPA with `try_files $uri /index.html` + +### GitHub Actions CI (`ci.yml`) + +Three jobs run on every push and PR to `main`: + +| Job | Duration | What it does | +|-----|----------|--------------| +| `backend-import-healthcheck` | ~30s | Install Python deps, verify imports, start server, curl `/health` | +| `frontend-build` | ~60s | `npm ci` + `npm run build` (Vite production build) | +| `docker-compose-validate` | ~10s | `docker compose config --quiet` | + +**Gap**: No unit tests, no linting in CI, no docs validation. + +--- + +## Security Assessment + +### Critical Findings + +| Severity | Finding | Location | Recommendation | +|----------|---------|----------|----------------| +| 🔴 HIGH | No authentication on any endpoint | `main.py` (all routes) | Add JWT middleware; use `python-jose` + `passlib` already in deps | +| 🔴 HIGH | WebSocket has no auth | `main.py:/ws` | Add token query param check | +| 🟠 MEDIUM | CORS allows all origins (`*`) | `main.py:121` | Restrict to frontend domain in production | +| 🟠 MEDIUM | `python-jose 3.4.0` has known CVEs | `requirements.txt` | Upgrade to latest; or switch to `PyJWT` | +| 🟡 LOW | No rate limiting on NLP endpoint | `routes/nlp.py` | Add `slowapi` or nginx rate limiting | +| 🟡 LOW | No input validation on free-text fields | NLP query body | Add max-length constraints | +| 🟡 LOW | HTTP management interface flagged in config | `anomaly_detector.py` patterns | Already detected — ensure mitigation action fires | + +### Positive Security Findings + +- ✅ `allow_credentials=False` in CORS — safe with wildcard origins +- ✅ `python-jose` and `passlib` already in requirements — auth scaffolding ready +- ✅ Immutable audit log for config changes +- ✅ `field(ge=0, le=100)` validators on all percentage fields +- ✅ Enum-based type system prevents injection via type fields +- ✅ No hardcoded secrets in codebase + +--- + +## Data Model Review + +### Pydantic v2 Models (`core/models.py`) + +| Model | Fields | Validators | Notes | +|-------|--------|------------|-------| +| `Device` | 15 | `ge/le` on usage fields | ✅ Well-defined | +| `NetworkLink` | 8 | `ge/le` on % fields | ✅ | +| `ThreatAlert` | 12 | `ge/le` on confidence | ✅ | +| `Alert` | 13 | None | ✅ | +| `ConfigChange` | 11 | None | ✅ | +| `SoftwareUpdate` | 11 | None | ✅ | +| `NLPQuery` | 2 | None | ⚠️ No max_length on query string | +| `NLPResponse` | 4 | None | ✅ | +| `DeviceHealth` | 5 | `ge/le` on health_score | ✅ | + +### Enumerations + +10 enums defined: `DeviceType`, `DeviceStatus`, `LinkStatus`, `ThreatSeverity`, `ThreatType`, `ThreatStatus`, `ConfigChangeType`, `ConfigChangeStatus`, `UpdateStatus`, `AlertType`. + +All use `str, Enum` for JSON serialization compatibility. + +--- + +## API Completeness + +### Missing / Incomplete Endpoints + +| Gap | Impact | Recommendation | +|-----|--------|----------------| +| `DELETE /api/alerts/{id}` | Can only acknowledge, not delete | Add delete endpoint | +| `PUT /api/devices/{id}` | Cannot update device metadata | Add update endpoint | +| `GET /api/devices/search?q=` | No search/filter capability | Add query params | +| `POST /api/auth/login` | No authentication | Implement JWT auth | +| `GET /api/users` | No user management | Add user RBAC | +| `GET /api/audit-log` | Config changes logged but not queryable separately | Add audit endpoint | +| Pagination on list endpoints | All lists return all records | Add `?skip=&limit=` | +| `GET /api/bgp/sessions/{id}` | Individual session detail missing | Add detail endpoint | + +### NLP Service Gaps + +The NLP service uses keyword/intent matching — it is not connected to an actual LLM. It recognises approximately 15 intent patterns. Production use would require: +- Integration with OpenAI / Azure OpenAI / local LLM +- Conversation history / context tracking +- Streaming response support + +--- + +## Code Quality + +### Backend + +| Aspect | Rating | Notes | +|--------|--------|-------| +| Type annotations | ✅ Excellent | `from __future__ import annotations` used throughout | +| Docstrings | ✅ Good | Key functions documented | +| Error handling | ✅ Good | HTTPException used consistently | +| Logging | ✅ Good | Structured logging with levels | +| Code organisation | ✅ Excellent | Clean router/service/model separation | +| Testing | ❌ None | Zero unit tests | +| Linting | ⚠️ No CI lint step | Code style not enforced in CI | + +### Frontend + +| Aspect | Rating | Notes | +|--------|--------|-------| +| TypeScript coverage | ✅ Good | All pages typed; adapters handle field mapping | +| Component decomposition | ⚠️ Fair | Pages are large (200–500 lines); candidate for sub-component extraction | +| Error handling | ⚠️ Fair | Try/catch in fetch calls but no error boundary | +| Loading states | ✅ Good | Loading spinners shown during data fetches | +| Accessibility | ❌ Poor | No ARIA labels, no keyboard navigation beyond defaults | +| Testing | ❌ None | No unit or E2E tests | +| Bundle size | ⚠️ Unknown | Vite build not analysed — Recharts can be large | + +--- + +## Gaps & Risks + +### High Priority + +1. **No authentication** — Any user can read all data and trigger mitigations +2. **In-memory only** — All data lost on restart; no persistence layer +3. **No tests** — No safety net for refactoring or new features +4. **python-jose CVEs** — Upgrade required before enabling auth + +### Medium Priority + +5. **Hardcoded `DEVICE_ID_MAP`** in `Config.tsx` — breaks when devices are added +6. **No pagination** — List endpoints return all records; performance risk at scale +7. **NLP is keyword-only** — No real AI/LLM integration +8. **BGP/Circuits/Workflows are 100% mock** — Not connected to real data + +### Low Priority + +9. **No accessibility (a11y)** — Screen readers unsupported +10. **No bundle analysis** — Potential large JS bundle +11. **No metrics export** — No Prometheus endpoint for infrastructure monitoring +12. **CORS wildcard in production** — Should be restricted + +--- + +## Recommendations + +### Immediate (Sprint 1) + +- [ ] Add JWT authentication (`POST /api/auth/login`, `Depends(get_current_user)`) +- [ ] Upgrade `python-jose` or migrate to `PyJWT` +- [ ] Add pagination (`?skip=0&limit=50`) to all list endpoints +- [ ] Write unit tests for services and anomaly detector +- [ ] Add ESLint to frontend CI step + +### Short-term (Sprint 2) + +- [ ] Replace in-memory store with SQLite (SQLAlchemy + Alembic) +- [ ] Fix hardcoded `DEVICE_ID_MAP` in Config.tsx — use device search API +- [ ] Add React error boundary component +- [ ] Add Prometheus metrics endpoint to backend +- [ ] Wire BGP/Circuits/Workflows to real or simulated real-time data + +### Medium-term (Sprint 3) + +- [ ] Integrate real LLM (OpenAI API or local Ollama) for NLP +- [ ] Add SNMP polling service for real device data +- [ ] Add WebSocket authentication +- [ ] Add accessibility (ARIA labels, keyboard navigation) +- [ ] Add E2E tests with Playwright + +### Long-term + +- [ ] Kubernetes deployment manifests +- [ ] Multi-tenant support +- [ ] SSO / SAML integration +- [ ] Real-time alerting via PagerDuty/OpsGenie webhook +- [ ] Network graph analytics (community detection, shortest path) + +--- + +*This report was auto-generated by the AI investigation agent. See [TODO.md](TODO.md) for the prioritised task list and [ai-instructions.md](ai-instructions.md) for AI contribution guidelines.* diff --git a/docs/screenshots.md b/docs/screenshots.md new file mode 100644 index 0000000..66cc8f8 --- /dev/null +++ b/docs/screenshots.md @@ -0,0 +1,87 @@ +# netAI — Application Screenshots + +> Generated: 2026-04-06 | All screenshots taken at 1440×900 resolution + +--- + +## 01 — Dashboard + +The main dashboard showing KPI cards, network health score, recent alerts, and device health by layer. + +![Dashboard](screenshots/01-dashboard.png) + +--- + +## 02 — Network Topology + +Interactive SVG network map with colour-coded device nodes, link health, and click-to-inspect panel. + +![Topology](screenshots/02-topology.png) + +--- + +## 03 — Threat Detection + +Active threat list with severity distribution pie chart, anomaly timeline, and mitigation actions. + +![Threats](screenshots/03-threats.png) + +--- + +## 04 — Device Health + +Per-device CPU/memory/disk utilisation bars, performance charts, and failure prediction badges. + +![Device Health](screenshots/04-devices.png) + +--- + +## 05 — Alerts Center + +Unified alert center with severity filters, acknowledgment flow, and alert statistics. + +![Alerts](screenshots/05-alerts.png) + +--- + +## 06 — AI Assistant (ChatOps / NLP) + +Natural language interface for network operations — query the network in plain English. + +![AI Assistant](screenshots/06-nlp.png) + +--- + +## 07 — Configuration Management + +Per-device config viewer, compliance violation list, change history, and audit/apply actions. + +![Configuration](screenshots/07-config.png) + +--- + +## 08 — Software Lifecycle + +Firmware inventory, CVE tracking, upgrade scheduling, and upgrade job status tracking. + +![Software](screenshots/08-software.png) + +--- + +## 09 — BGP Monitor + +BGP session management with route hijack detection and one-click resolve workflow. + +![BGP Monitor](screenshots/09-bgp.png) + +--- + +## 10 — Reports & Analytics + +Historical bandwidth trends, per-device 30-day uptime charts, and incident rollup report. + +![Reports](screenshots/10-reports.png) + +--- + +*Screenshots are stored in `docs/screenshots/`. For the complete feature walkthrough, see [user-guide.md](user-guide.md).* diff --git a/docs/screenshots/01-dashboard.png b/docs/screenshots/01-dashboard.png new file mode 100644 index 0000000..3e27ee8 Binary files /dev/null and b/docs/screenshots/01-dashboard.png differ diff --git a/docs/screenshots/02-topology.png b/docs/screenshots/02-topology.png new file mode 100644 index 0000000..5bedf67 Binary files /dev/null and b/docs/screenshots/02-topology.png differ diff --git a/docs/screenshots/03-threats.png b/docs/screenshots/03-threats.png new file mode 100644 index 0000000..d43a008 Binary files /dev/null and b/docs/screenshots/03-threats.png differ diff --git a/docs/screenshots/04-devices.png b/docs/screenshots/04-devices.png new file mode 100644 index 0000000..26a7239 Binary files /dev/null and b/docs/screenshots/04-devices.png differ diff --git a/docs/screenshots/05-alerts.png b/docs/screenshots/05-alerts.png new file mode 100644 index 0000000..940026a Binary files /dev/null and b/docs/screenshots/05-alerts.png differ diff --git a/docs/screenshots/06-nlp.png b/docs/screenshots/06-nlp.png new file mode 100644 index 0000000..a63f0f8 Binary files /dev/null and b/docs/screenshots/06-nlp.png differ diff --git a/docs/screenshots/07-config.png b/docs/screenshots/07-config.png new file mode 100644 index 0000000..548e9cb Binary files /dev/null and b/docs/screenshots/07-config.png differ diff --git a/docs/screenshots/08-software.png b/docs/screenshots/08-software.png new file mode 100644 index 0000000..924a812 Binary files /dev/null and b/docs/screenshots/08-software.png differ diff --git a/docs/screenshots/09-bgp.png b/docs/screenshots/09-bgp.png new file mode 100644 index 0000000..df0b57a Binary files /dev/null and b/docs/screenshots/09-bgp.png differ diff --git a/docs/screenshots/10-reports.png b/docs/screenshots/10-reports.png new file mode 100644 index 0000000..8ab320b Binary files /dev/null and b/docs/screenshots/10-reports.png differ