# BUILD NOTES — SENTINEL Trainer

## What this is
A runnable Flask prototype of a KnowBe4-style phishing-awareness training
platform. It renders dummy phishing lures, "sends" them to opted-in users
(render + log only), tracks opens/clicks/reports, and scores each user.

## Architecture
- `trainer.py` — single-file Flask app.
  - **Template library** (`TEMPLATES`): 5 dummy lures (3 email, 2 SMS) across
    easy/medium/hard difficulty. Each has a `red_flags` list surfaced on the
    lesson page after a click, so the exercise teaches.
  - **Consent-gated send** (`send_simulation`): iterates users, skips anyone not
    `opted_in`, renders the message with an internal `/clicked/...` link, and
    writes a `sends` row. Returns `sent` and `skipped_no_consent`. No SMTP/SMS.
  - **Tracking endpoints**: `/opened`, `/clicked` (shows the lesson page),
    `/report`. Each is idempotent (won't double-count).
  - **Scoring** (`training_score`): `100 − click_rate*70 + report_rate*20`,
    clamped 0..100.
  - **Persistence**: SQLite tables `users`, `campaigns`, `sends`.
- `templates/dashboard.html` — dark security-console dashboard: KPI cards,
  per-campaign click/report rates with bars, per-user training scores, and the
  template library. Consent + "no real payloads" banners are always visible.
- `templates/caught.html` — the friendly lesson page shown when a trainee clicks
  a simulated link. States plainly it was a training test with no real payload,
  lists the red flags they missed, and offers an "I would report this" action.

## Safety review (against ../shared/BRIEF.md, guardrail 5)
- **No real payload:** simulated links resolve ONLY to the app's own `/clicked`
  route. Nothing downloadable/executable is ever served.
- **Nothing is sent:** grep confirms no `smtplib`, no `requests`, no `urllib`
  egress inside `trainer.py`. "Send" = render + DB row. The only HTTP is the
  local Flask server and the optional seeder hitting `localhost`.
- **Consent enforced:** send loop hard-skips non-opted-in users. The seed set
  deliberately includes 2 non-consenting users; every campaign reports
  `skipped_no_consent=2`, proving they are never targeted.
- **Synthetic data:** all users/emails are fictional (`example.com`).
- **Visible disclosure:** dashboard and every lesson page carry banners stating
  this is a training simulator with no real payloads and opt-in only.

## Verification performed
- `python demo_seed.py --offline` → 9 users (7 opted-in), 4 campaigns,
  sent=28, clicked/reported populated; 2 skipped per campaign.
- Started server, ran `demo_seed.py` which drove the LIVE HTTP endpoints
  (`/opened`, `/clicked`, `/report`): 9 clicks, 10 reports over 28 sends.
- `/api/stats` returned click_rate=32, consenting_users=7.
- `/api/users` returned correct per-user scores (e.g. clean user = 100,
  clicker < 100; non-consenting users show as not enrolled).
- `GET /clicked/<campaign>/<token>` returned the lesson page containing
  "TRAINING SIMULATION", "training exercise", "There was no real payload".
- `GET /` returned HTTP 200.
- Dashboard screenshot could not be captured (remote screenshotter can't reach
  sandbox localhost; Playwright not installed), but the full HTTP contract is
  verified.

## Possible next steps (not built)
- Real (consented) delivery via a pluggable transport with rate limits.
- Scheduled drip campaigns and difficulty auto-escalation per user.
- Manager rollups and Tier 3 policy (mandatory quarterly training).
