From 8c0acb06e1fa9c4bd1e30c23418e394a5ffc864e Mon Sep 17 00:00:00 2001 From: Roberto Date: Wed, 8 Jul 2026 08:52:44 -0500 Subject: [PATCH 01/21] docs: add Mermaid architecture diagram suite + log Phase 5.5 parallel-track split MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Six diagrams (docs/architecture.md, GitHub-native mermaid): system architecture, DB ERD (from auth-schema.ts), two-layer auth flow, CI/CD pipeline, shared-VPS topology, branching/promotion model. Accurate to the real repo, not idealized. BRAIN: logged the 5.5 split so both sessions see the map — this session owns diagrams + integration duty + the (deferred) responsive audit; the UI session owns feature/ui-landing-polish (aurora-tech v2 + landing), screenshot-gated. --- docs/ai-process/BRAIN.md | 14 ++++ docs/architecture.md | 172 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 186 insertions(+) create mode 100644 docs/architecture.md diff --git a/docs/ai-process/BRAIN.md b/docs/ai-process/BRAIN.md index fb2ed4e52..8e8eeb34b 100644 --- a/docs/ai-process/BRAIN.md +++ b/docs/ai-process/BRAIN.md @@ -32,6 +32,20 @@ base**. Its front-matter auto-triggers broadly and WILL offer its 161 palettes / 3. **Every visual change ships behind Roberto's screenshot approval BEFORE merge** (Phase 5.5 exit crit). 4. **Gated to Phase 5.5** — do not let it drive Phase 5 hardening. +## ⚡ Phase 5.5 — PARALLEL TRACKS (active split 2026-07-08 — BOTH sessions read this) +Two sessions run concurrently with **no file collision**: +- **Diagrams + integration session (this one):** Mermaid diagram suite in `docs/architecture.md` + (architecture · DB ERD · auth flow · CI/CD · VPS topology · git flow — GitHub renders ` ```mermaid ` + natively). Owns **integration duty**: reviews + merges the UI session's PR. Runs the **responsive audit + @360/375/768/1024/1440 LAST**, only AFTER the new landing lands in develop (auditing the old UI = waste). + Touches: `docs/**` only. +- **UI session (isolated worktree `feature/ui-landing-polish`):** aurora-tech **v2** tokens + landing + redesign, **screenshot-gated by Roberto before merge**. Must honor the ui-ux-pro-max HARD RULES above + (aurora-tech is LAW). Touches: `src/app/(marketing)/**` + theme preset sources; NOT docs, NOT the + auth/security surface. +- **Collision guard:** diagrams = docs-only; UI = marketing/theme-only. The responsive audit blocks on the + UI merge. If either track needs a shared file, coordinate here first. + ## ⚠ Pending Roberto actions (not code — external/his account) 0. **VPS SSH hardening (B.3 findings, verified `sshd -T` 2026-07-08 — see docs/deployment.md).** Mostly solid (root is key-only `without-password`; ufw active deny-by-default 22/80/443; empty-pw off), BUT diff --git a/docs/architecture.md b/docs/architecture.md new file mode 100644 index 000000000..091492f58 --- /dev/null +++ b/docs/architecture.md @@ -0,0 +1,172 @@ +# Nova Analytics — Architecture + +Six diagrams of the running system. GitHub renders ` ```mermaid ` fences natively. +Each is generated from the real repo (`src/db/auth-schema.ts`, `src/proxy.ts`, +`.github/workflows/`, `deploy/`, `docs/deployment.md`) — not idealized. + +## 1. System architecture + +Request path (browser → TLS → app → DB) plus the CI-built image supply. + +```mermaid +flowchart LR + B["Browser"] -->|HTTPS| DNS["Cloudflare DNS
nova.robertobh.dev (A, DNS-only)"] + DNS --> NGINX + + subgraph vps["Hetzner CPX11 · 178.156.248.110"] + NGINX["nginx
TLS termination (certbot)"] + subgraph net["docker network: data"] + WEB["nova-web-1
Next.js 16 standalone · Node 22
mem_limit 512M"] + PG[("shared-postgres
Postgres 17 · mem_limit 384M
DB: nova")] + end + end + + GHCR["GHCR
ghcr.io/xdroberto/nova-analytics"] + + NGINX -->|"proxy 127.0.0.1:3000"| WEB + WEB -->|"Better Auth + Drizzle
127.0.0.1:5432"| PG + GHCR -.->|"docker pull :latest"| WEB +``` + +> Security headers (HSTS, X-Frame-Options DENY, nosniff, Referrer-Policy, Permissions-Policy) +> are set app-side in `next.config.mjs`, so they hold even without nginx. + +## 2. Database ERD + +Better Auth schema (`src/db/auth-schema.ts`). `verification` is standalone; `session` +and `account` cascade-delete with their `user`. + +```mermaid +erDiagram + user ||--o{ session : "has" + user ||--o{ account : "has" + + user { + text id PK + text email UK + text name + boolean email_verified + text image + timestamp created_at + timestamp updated_at + } + session { + text id PK + text user_id FK + text token UK + timestamp expires_at + text ip_address + text user_agent + timestamp created_at + } + account { + text id PK + text user_id FK + text account_id + text provider_id + text password + text access_token + timestamp created_at + } + verification { + text id PK + text identifier + text value + timestamp expires_at + timestamp created_at + } +``` + +## 3. Auth flow (two-layer session check) + +The edge proxy is optimistic (cookie presence only); the dashboard layout does the +authoritative `getSession()` against the DB. + +```mermaid +sequenceDiagram + actor U as User + participant E as Edge proxy + participant A as Better Auth + participant L as Dashboard layout + participant DB as Postgres + + U->>A: POST /sign-in (email + password) + A->>DB: verify creds, create session row + A-->>U: Set-Cookie better-auth.session_token (HttpOnly) + + U->>E: GET /dashboard/default (cookie) + alt no cookie + E-->>U: 307 redirect to /login + else cookie present (optimistic pass) + E->>L: forward + L->>DB: auth.api.getSession() + alt valid and unexpired + L-->>U: render dashboard + else forged or expired + L-->>U: redirect via /api/session/clear to /login + end + end +``` + +## 4. CI/CD pipeline + +`.github/workflows/ci.yml` gates every change; `deploy.yml` ships on `main`. + +```mermaid +flowchart TD + PUSH["git push / PR"] --> EV{"event"} + EV -->|pull_request| PRJOBS["CI: commits (commitlint base..head)
+ quality + e2e"] + EV -->|"push (main/develop)"| CIJOBS["CI: quality + e2e
(commits job skipped)"] + + CIJOBS -->|"branch = main"| DEP + PRJOBS -.->|"after merge to main"| DEP + + subgraph DEP["deploy.yml — main only"] + BUILD["build-push → GHCR :latest + :sha"] --> SCP["scp deploy/remote-deploy.sh"] + SCP --> SSH["SSH: pull → compose up → health-retry"] + SSH -->|healthy| PRUNE["prune old image ✓"] + SSH -->|unhealthy| RB["rollback to previous image · exit 1 ✗"] + end +``` + +> `quality` = lint + `tsc` + `test:unit` + branding gate + `next build`. +> `e2e` = Postgres service + `drizzle-kit push` + Playwright (incl. the security bypass suite). + +## 5. VPS topology (shared host, isolated Nova) + +One CPX11 hosts Nova alongside co-tenants; isolation is by container memory limits + swap +(ADR-003 amendment). `ufw` allows only 22/80/443. + +```mermaid +flowchart TB + subgraph vps["Hetzner CPX11 · Ubuntu 24.04 · ~2GB RAM + 3G swap · ufw 22/80/443"] + NGINX["nginx — TLS for all vhosts"] + subgraph nova["Nova (mem-limited)"] + W["nova-web-1 · 512M"] + P[("shared-postgres · 384M")] + end + subgraph co["Co-tenants"] + PORT["portfolio (static)"] + SE["sideeffects (static)"] + MOON["moonhouse (Python)"] + IM["imcore (docker)"] + end + end + NGINX --> W + NGINX --> PORT + NGINX --> SE + NGINX --> MOON + W --> P +``` + +## 6. Branching & promotion model + +`main` is deployable; `develop` integrates; `feature/*` (and `chore/*`) branch off develop. +A promotion PR to `main` runs the full CI incl. the commitlint gate, then merging deploys. + +```mermaid +flowchart LR + F["feature/* · chore/*"] -->|PR| D["develop
(integration)"] + D -->|"promotion PR
commitlint gate + full CI"| M["main
(deployable)"] + M -->|"push → deploy.yml"| PROD["production
nova.robertobh.dev"] +``` From 22dca0933a861ada562a93f54c483ff81782dbba Mon Sep 17 00:00:00 2001 From: Roberto Date: Wed, 8 Jul 2026 08:59:48 -0500 Subject: [PATCH 02/21] docs(process): close Phase 5 (promoted, LIVE) + record 5.5 diagram suite verified on GitHub --- docs/ai-process/BRAIN.md | 13 ++++++++----- docs/ai-process/ROADMAP.md | 6 ++++-- docs/ai-process/SESSION-LOG.md | 1 + 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/docs/ai-process/BRAIN.md b/docs/ai-process/BRAIN.md index 8e8eeb34b..32bd89ab9 100644 --- a/docs/ai-process/BRAIN.md +++ b/docs/ai-process/BRAIN.md @@ -4,11 +4,14 @@ > every significant action, (4) never reconstruct state from chat memory. ## Current position -- Phase: 4 (Deploy) ✅ **FULLY CLOSED** (promoted to main via PR #6 = merge 4325946; UptimeRobot monitor - now live). **LIVE at https://nova.robertobh.dev** — TLS, health ok, auth works, security headers live, - push-to-deploy verified end-to-end from a PR merge (~3m09s: build-push 2m46s + deploy 16s). -- Next: **Phase 5 (Hardening)** — Vitest units, cross-browser/mobile QA matrix, adversarial review; plus - the queued repo-hygiene batch (see backlog) + repo/landing/README polish. +- Phase: **5 (Hardening) ✅ CLOSED + LIVE in production** (promoted via PR #7 = merge b531e52; deploy + success, health ok). Shipped: Vitest + `evaluateHealth`, the security bypass suite, explicit Better Auth + rate limiting, supply-chain `ignore-scripts`, Node-22 toolchain pin, and an adversarial review (1 HIGH + auth bypass — orphaned /chat + /mail — fixed). **LIVE at https://nova.robertobh.dev.** +- Now: **Phase 5.5 (UI/UX & diagrams) — PARALLEL TRACKS** (see the ⚡ section above). THIS session: diagram + suite ✅ DONE (6, verified rendering on GitHub); responsive audit @5 breakpoints DEFERRED until the new + landing lands; integration duty = review/merge the UI session's PR. UI session runs on + `feature/ui-landing-polish` (aurora-tech v2 + landing, screenshot-gated). - Session note: a fresh session boots from ROADMAP→BRAIN→latest SESSION-LOG with zero chat context. ## ✅ Commitlint gate — first live run RESOLVED (PR #6) diff --git a/docs/ai-process/ROADMAP.md b/docs/ai-process/ROADMAP.md index 94b7fc205..5e43c6590 100644 --- a/docs/ai-process/ROADMAP.md +++ b/docs/ai-process/ROADMAP.md @@ -19,8 +19,8 @@ | 2 | Whitelabel | ✅ | Branding grep = 0 hits, CI-enforced | 1e6a511 | | 3 | Landing | ✅ | Lighthouse ≥90 mobile; CTA→signup works | 6cd239e | | 4 | Deploy | ✅ | Public HTTPS + push-to-deploy + audit gate + uptime monitoring (UptimeRobot **LIVE**) | af6625c | -| 5 | Hardening | ⬜ | Full suite green in CI; review findings fixed; **security bypass suite green** (no-cookie / invalid / expired → deny·redirect; auth rate-limit responds) | — | -| 5.5 | UI/UX polish & diagrams | ⬜ | (1) Mermaid diagram suite renders on GitHub — architecture · DB ERD · auth flow · CI/CD · VPS topology · git flow; (2) dashboard responsive audit passes @360/375/768/1024/1440 (no h-overflow, e2e-backed); (3) micro-interaction polish batch approved by Roberto via screenshots BEFORE merge | — | +| 5 | Hardening | ✅ | Full suite green in CI; review findings fixed; **security bypass suite green** (no-cookie / invalid / expired → deny·redirect; auth rate-limit responds) | b531e52 | +| 5.5 | UI/UX polish & diagrams | 🔄 | (1) ✅ Mermaid diagram suite renders on GitHub (6 diagrams, verified); (2) ⬜ dashboard responsive audit @360/375/768/1024/1440 (deferred to after the new landing lands); (3) ⬜ micro-interaction polish approved by Roberto via screenshots BEFORE merge | — | | 6 | Delivery | ⬜ | PRD checklist 100% + final audit gate | — | ## Milestone log @@ -37,3 +37,5 @@ | 2026-07-08 | Phase 4 closed: push-to-deploy demoed end-to-end (rollback-safe CD), security headers live, audit gate passed | af6625c | | 2026-07-08 | Phase 4 promoted to production (PR #6 develop→main); push-to-deploy verified from merge (~3m09s), health ok | 4325946 | | 2026-07-08 | UptimeRobot monitor LIVE (/api/health · 5-min · email + SSL-expiry) — **Phase 4 FULLY CLOSED** | 4325946 | +| 2026-07-08 | **Phase 5 CLOSED + promoted to production** (PR #7 develop→main, 19 commits); adversarial review 1 HIGH fixed; deploy success, health ok | b531e52 | +| 2026-07-08 | Phase 5.5 (1/3): Mermaid diagram suite (6) in docs/architecture.md — verified rendering on GitHub | 8c0acb0 | diff --git a/docs/ai-process/SESSION-LOG.md b/docs/ai-process/SESSION-LOG.md index db441735b..f8d66b536 100644 --- a/docs/ai-process/SESSION-LOG.md +++ b/docs/ai-process/SESSION-LOG.md @@ -15,3 +15,4 @@ | 2026-07-08 | Post-merge: prod deploy verify + Phase 4 FULL close | lead, devops, repo-steward | Roberto merged PR #6 (main=4325946). Push-to-deploy from merge verified: deploy run success ~3m09s (build-push 2m46s + deploy 16s); site serves (landing/login/health 200, `{"status":"ok","db":"up"}`). Pushed `chore/repo-hygiene` + merged→develop (BRAIN/SESSION-LOG reconverged; prune reversibility log archived to `evidence/branch-prune-2026-07-08.txt`). Disk: `docker builder prune -f` → 87%→74% (**~4GB real**, not the 21GB docker advertised — shared layers overcount). `gh repo set-default` → fork (root cause of the #59/#61 misread). **UptimeRobot LIVE → Phase 4 ✅ FULLY CLOSED** (ADR-004 monitoring section + ROADMAP milestones updated). | | 2026-07-08 | Phase 5 kickoff: hygiene batch + Vitest + security suite 🎥 | lead, qa-tester (hat), security (hat) | Phase 5.5 registered in ROADMAP (operator scope) + ui-ux-pro-max installed under HARD RULES. Hygiene: .nvmrc 22 + engines advisory, .gitattributes (churn=0, no blame-ignore needed), pnpm note. Vitest: evaluateHealth extracted via TDD (RED→GREEN), CI quality runs test:unit. Security suite: 6 e2e (headers regression, anon 307, get-session null, forged-cookie deny, **literal expired-session deny** via SQL expires_at rewind, 429 rate-limit LAST for per-IP ordering) + 6 proxy unit tests. Rate limiting enabled explicitly (prod-only default verified in BA 1.6.23 source; 10/60s on /sign-in/email). **Full e2e 10/10 local** 🎥. Incident: RED-run 500s = stale .next/dev Turbopack cache post-npm-churn (not an app bug); purge + documented. | | 2026-07-08 | Phase 5 B.4: adversarial review + load sanity | lead, reviewer (subagent), security (hat) | Capstone adversarial review over the +15 diff (dual mandate). **1 HIGH fixed:** orphaned unauthenticated `/chat`+`/mail` (allow-list matcher gap) deleted + regression + proxy caveat. **3 MED/LOW fixed:** rate limit 10→5/60s + honest comment, robust ≤5 429 test (shared-IP-safe), headers 5/5. **2 documented:** trustedProxies gap (not exploitable — 127.0.0.1-bound), no-RBAC. Reviewer confirmed forged/expired/ordering/health-refactor SOUND. Load (autocannon LIVE): p99 <75ms, 0 errors/~16k req, mem ~89/512MiB, no OOM. **e2e 11/11 (x2).** Verdict: promotable. | +| 2026-07-08 | Phase 5 promoted to prod + Phase 5.5 diagrams | lead, integration (hat) | Roberto merged PR #7 (main=b531e52; GitHub auto-deleted develop → re-created at 8c0acb0). **Phase 5 LIVE**: deploy run success, health ok. Working-tree flag resolved — the auth.ts/security.spec/limitations changes were the review fixes, already committed in f548ab0 (tree clean). Logged the Phase-5.5 **parallel-track split** in BRAIN (this session = diagrams + integration + deferred responsive audit; UI session = `feature/ui-landing-polish`). Built the **6-diagram Mermaid suite** (docs/architecture.md) and **verified all 6 render on GitHub** 🎥. | From 4046725beb9f2f0ce4522cdc3643a66153f4b844 Mon Sep 17 00:00:00 2001 From: Roberto Date: Wed, 8 Jul 2026 09:30:11 -0500 Subject: [PATCH 03/21] fix(docs): flatten VPS topology diagram for GitHub render + record SSH hardening applied MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit VPS diagram: removed the doubly-nested subgraphs (nova/co) that GitHub's mermaid renderer choked on; single-level subgraph now, grouping conveyed via node labels. SSH: PasswordAuthentication no + KbdInteractive no + X11Forwarding no applied (drop-in, sshd -t + reload, fresh key login verified — no lockout); fail2ban 1.0.2 active (sshd jail) — banned 6 brute-force IPs on first scan. deployment.md + BRAIN updated to reflect applied. --- docs/ai-process/BRAIN.md | 13 +++++++------ docs/architecture.md | 23 +++++++++++------------ docs/deployment.md | 22 ++++++++++++---------- 3 files changed, 30 insertions(+), 28 deletions(-) diff --git a/docs/ai-process/BRAIN.md b/docs/ai-process/BRAIN.md index 32bd89ab9..6e5e18d82 100644 --- a/docs/ai-process/BRAIN.md +++ b/docs/ai-process/BRAIN.md @@ -50,12 +50,13 @@ Two sessions run concurrently with **no file collision**: UI merge. If either track needs a shared file, coordinate here first. ## ⚠ Pending Roberto actions (not code — external/his account) -0. **VPS SSH hardening (B.3 findings, verified `sshd -T` 2026-07-08 — see docs/deployment.md).** Mostly - solid (root is key-only `without-password`; ufw active deny-by-default 22/80/443; empty-pw off), BUT - two open findings: **`PasswordAuthentication yes`** (should be `no` — key access proven via CD, no - expected lockout) and **fail2ban NOT installed**. Not auto-applied — live-sshd edits risk lockout, and - it's Roberto's box. Recommend: set `PasswordAuthentication no` (with `sshd -t` + reload + keep session - open) + install fail2ban. App-layer brute-force already covered by the new Better Auth rate limit. +0. ✅ **VPS SSH hardening — APPLIED (2026-07-08, with Roberto's lifeline session + anti-lockout protocol).** + `PasswordAuthentication no` + `KbdInteractiveAuthentication no` + `X11Forwarding no` (drop-in + `99-nova-hardening.conf`; `sshd -t` validated → `reload`; a fresh key login verified → no lockout; + `PubkeyAuthentication yes` intact). fail2ban 1.0.2 active (`[sshd]` jail, systemd backend) — validated + the finding on install: the port was under active brute-force, **6 IPs banned / 57 failed attempts on + the first scan.** Details in docs/deployment.md. Root stays key-only; ufw 22/80/443. No Roberto action + left here. 1. ✅ **UptimeRobot — LIVE (2026-07-08).** HTTP(s) monitor on `/api/health`, 5-min interval, email alerts, SSL-expiry watch included (Roberto's account; screenshot captured). Was the last Phase-4 item → **Phase 4 now FULLY CLOSED.** diff --git a/docs/architecture.md b/docs/architecture.md index 091492f58..d47aa74a4 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -139,18 +139,14 @@ One CPX11 hosts Nova alongside co-tenants; isolation is by container memory limi ```mermaid flowchart TB - subgraph vps["Hetzner CPX11 · Ubuntu 24.04 · ~2GB RAM + 3G swap · ufw 22/80/443"] - NGINX["nginx — TLS for all vhosts"] - subgraph nova["Nova (mem-limited)"] - W["nova-web-1 · 512M"] - P[("shared-postgres · 384M")] - end - subgraph co["Co-tenants"] - PORT["portfolio (static)"] - SE["sideeffects (static)"] - MOON["moonhouse (Python)"] - IM["imcore (docker)"] - end + subgraph vps["Hetzner CPX11 · Ubuntu 24.04 · 2GB RAM + 3G swap · ufw 22/80/443 · fail2ban"] + NGINX["nginx — TLS, all vhosts"] + W["nova-web-1 · 512M · Nova"] + P[("shared-postgres · 384M · Nova")] + PORT["portfolio · static"] + SE["sideeffects · static"] + MOON["moonhouse · Python"] + IM["imcore · docker"] end NGINX --> W NGINX --> PORT @@ -159,6 +155,9 @@ flowchart TB W --> P ``` +> Nova is memory-limited (`web 512M` + `postgres 384M`) for blast-radius isolation on the shared host; +> `imcore` runs independently (not fronted by nginx). + ## 6. Branching & promotion model `main` is deployable; `develop` integrates; `feature/*` (and `chore/*`) branch off develop. diff --git a/docs/deployment.md b/docs/deployment.md index cf6f93040..e9455685b 100644 --- a/docs/deployment.md +++ b/docs/deployment.md @@ -111,20 +111,22 @@ Verified read-only, recorded honestly (not assumed): | `PermitEmptyPasswords` | `no` | ✅ | | `KbdInteractiveAuthentication` | `no` | ✅ | | `MaxAuthTries` | `6` | ✅ | -| **`PasswordAuthentication`** | **`yes`** | ⚠️ **open** — should be `no` (all access is key-based) | -| **fail2ban** | **not installed** | ⚠️ no SSH brute-force auto-ban | -| `X11Forwarding` | `yes` | ⓘ minor — unnecessary on a server | +| **`PasswordAuthentication`** | **`no`** (applied 2026-07-08 via drop-in) | ✅ key-only | +| **fail2ban** | **active** (1.0.2 · sshd jail · systemd backend) | ✅ auto-bans brute-force | +| `X11Forwarding` | `no` (applied 2026-07-08) | ✅ | | `ufw` | active; default deny incoming; only `22/tcp` + `80,443/tcp` (Nginx Full) allowed | ✅ | | OS | Ubuntu 24.04.4 LTS | — | -**Open findings (operator decision — not auto-applied; live-sshd changes risk lockout):** -1. `PasswordAuthentication no` — key access is proven (CD deploys succeed via key), so this closes the - password brute-force surface with no expected lockout. Apply in `/etc/ssh/sshd_config.d/` + `sshd -t` + - reload (keep the current session open to verify before disconnecting). -2. Install fail2ban (`sshd` jail) for defense-in-depth on port 22. -3. Optional: `X11Forwarding no`. +**Findings — APPLIED 2026-07-08** (anti-lockout protocol: `sshd -t` validate → `reload` (not restart) → +fresh key-connection verified before trusting the change): +1. ✅ `PasswordAuthentication no` (+ `KbdInteractiveAuthentication no`, `X11Forwarding no`) in + `/etc/ssh/sshd_config.d/99-nova-hardening.conf`. `PubkeyAuthentication yes` intact; a new key-based + login succeeded post-reload → no lockout. +2. ✅ fail2ban 1.0.2 installed + enabled; `[sshd]` jail (systemd backend, bantime 1h, maxretry 5, + `ignoreip` localhost). Validated the finding on install — the port was under active brute-force: + fail2ban banned 6 IPs (57 failed attempts) on the first scan. -App-layer brute-force is separately mitigated by Better Auth rate limiting (`/sign-in/email` 10/60s). +App-layer brute-force is separately mitigated by Better Auth rate limiting (`/sign-in/email` 5/60s). ## Load sanity (autocannon vs LIVE, 2026-07-08) From ab42bc2fbf530bdf841345736606d48f0bc117f4 Mon Sep 17 00:00:00 2001 From: Roberto Date: Wed, 8 Jul 2026 09:30:37 -0500 Subject: [PATCH 04/21] feat(brand): aurora-tech v2 tokens (operator design upgrade) Palette from Roberto's landing design (docs/design-reference/landing-v2.html) becomes the official aurora-tech v2 tokens. The dark theme IS the operator design; light keeps its base surfaces and adopts the violet brand accent. - background #05060E, foreground #E8EAF6, card #0F1224 (glass in component) - primary #6C5CE7 (violet); primary-foreground #FFF (WCAG AA 4.86; starlight 4.06 fails) - muted-foreground #9AA3C7 (AA 8.1); new slots --foreground-tertiary (#6E779B, AA-fixed 4.41->4.59) and --foreground-faint (#4D5578, marquee, WCAG 1.4.3 exempt) - border/input tinted lavender #94A3FF (was plain white) - ring + charts conserve nova-cyan #22D3EE and nova-green #34D399 All contrast pairs WCAG 2.x AA-verified; footer copy raised #5A6288->#717899. Dark = operator design; light adopts brand accent. Dev server compiles clean (landing 200). Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_019HXye76z6KXmV7WFirU3sk --- docs/ai-process/BRAIN.md | 14 +++++- src/app/globals.css | 92 +++++++++++++++++++++--------------- src/lib/preferences/theme.ts | 4 +- 3 files changed, 68 insertions(+), 42 deletions(-) diff --git a/docs/ai-process/BRAIN.md b/docs/ai-process/BRAIN.md index fb2ed4e52..65a8848c2 100644 --- a/docs/ai-process/BRAIN.md +++ b/docs/ai-process/BRAIN.md @@ -20,12 +20,22 @@ conventional-commits-parser@6.4.0"). Fixed by regenerating with npm 10 (`fix(deps)` 4bc3d81). See Decisions log + the Phase-5 hygiene backlog (node/npm pin, to prevent recurrence). +## 🎨 Parallel UI session (Phase 5.5 — landing redesign) — ACTIVE +A separate UI/UX session runs the landing redesign on branch `feature/ui-landing-polish` in an +**isolated git worktree** (`../nova-analytics-landing`, off origin/develop). Scope: ONLY +`src/app/(marketing)/**`, design tokens (`src/app/globals.css`), design-reference evidence, and this +note. It does NOT touch auth/proxy/deploy/CI/scripts. Foundation = aurora-tech **v2** tokens (operator +design, see Decisions log). Screenshot-gated per batch; integration + promotion are the Lead's/Roberto's. +The Lead's Phase-5 work on `develop` proceeds independently (no app-code overlap by construction). + ## ⚖ ui-ux-pro-max skill — HARD RULES (installed 2026-07-08, for Phase 5.5 only) Installed globally (`~/.claude/skills/ui-ux-pro-max`, MIT, nextlevelbuilder) as a **read-only knowledge base**. Its front-matter auto-triggers broadly and WILL offer its 161 palettes / 57 font pairings / "design systems" — **those are OFF-LIMITS for Nova.** Non-negotiable: -1. **Nova's aurora-tech design tokens are LAW.** The skill NEVER introduces a new palette, font, style, or - design-system; ignore every color/typography/style output it produces. +1. **Nova's aurora-tech v2 design tokens are LAW** (operator redesign, Phase 5.5 — palette extracted from + `docs/design-reference/landing-v2.html`, single source of truth in `src/app/globals.css`). The skill + NEVER introduces a new palette, font, style, or design-system; ignore every color/typography/style + output it produces. 2. **Consult ONLY for:** UX guideline checklists, dashboard chart-type selection guidance, WCAG/contrast + interaction-timing rules, and anti-pattern lists. Knowledge only — do NOT run its Python scripts (supply-chain discipline; the documented rules stand alone). diff --git a/src/app/globals.css b/src/app/globals.css index fd02aa6cd..d4b9f5133 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -48,13 +48,18 @@ --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); --color-sidebar-border: var(--sidebar-border); --color-sidebar-ring: var(--sidebar-ring); + --color-foreground-tertiary: var(--foreground-tertiary); + --color-foreground-faint: var(--foreground-faint); --font-heading: var(--font-sans); --font-sans: var(--font-sans); } -/* Default theme: Nova Analytics "aurora tech" (spec §4 — the single source of truth -for brand color). Brand anchors: nova-void #0B0D1A · nova-indigo #5B5FEF · -nova-violet #8B5CF6 · nova-cyan #22D3EE · nova-starlight #F8FAFC · nova-green #34D399. +/* Default theme: Nova Analytics "aurora tech" v2 (spec §4 + Phase 5.5 operator redesign — +the single source of truth for brand color; palette derived from +docs/design-reference/landing-v2.html). v2 brand anchors: nova-void #05060E · +nova-violet #6C5CE7 · nova-violet-light #7C6CF0 · nova-violet-deep #5646D6 · +nova-cyan #22D3EE · nova-green #34D399 · nova-starlight #E8EAF6. The dark theme IS the +operator design; light keeps its base surfaces and adopts the violet brand accent. Used when no data-theme-preset is set or when 'default' is selected; there is no separate default.css file. */ :root { @@ -63,7 +68,7 @@ separate default.css file. */ --card-foreground: oklch(0.155 0.033 266); --popover: oklch(1 0 0); --popover-foreground: oklch(0.155 0.033 266); - --primary: oklch(0.575 0.235 277); /* nova-indigo */ + --primary: oklch(0.568 0.202 283); /* nova-violet v2 #6C5CE7 (brand accent, unified) */ --primary-foreground: oklch(0.984 0.003 247.858); /* nova-starlight */ --secondary: oklch(0.955 0.012 277); --secondary-foreground: oklch(0.31 0.09 277); @@ -74,20 +79,25 @@ separate default.css file. */ --destructive: oklch(0.577 0.245 27.325); --border: oklch(0.91 0.012 260); --input: oklch(0.91 0.012 260); - --ring: oklch(0.575 0.235 277); - --chart-1: oklch(0.575 0.235 277); /* nova-indigo */ + --ring: oklch(0.568 0.202 283); /* violet focus */ + --chart-1: oklch(0.568 0.202 283); /* nova-violet v2 */ --chart-2: oklch(0.715 0.143 215.221); /* nova-cyan (deepened for light bg) */ --chart-3: oklch(0.606 0.25 292.717); /* nova-violet */ --chart-4: oklch(0.696 0.17 162.48); /* nova-green (deepened for light bg) */ --chart-5: oklch(0.45 0.08 270); --sidebar: oklch(0.972 0.006 255); --sidebar-foreground: oklch(0.155 0.033 266); - --sidebar-primary: oklch(0.575 0.235 277); + --sidebar-primary: oklch(0.568 0.202 283); --sidebar-primary-foreground: oklch(0.984 0.003 247.858); --sidebar-accent: oklch(0.93 0.02 275); --sidebar-accent-foreground: oklch(0.31 0.09 277); --sidebar-border: oklch(0.91 0.012 260); - --sidebar-ring: oklch(0.575 0.235 277); + --sidebar-ring: oklch(0.568 0.202 283); + + /* Secondary-text slate scale (Phase 5.5 v2 — new slots). Light values are derived + for light surfaces; the operator design itself is dark (see .dark). */ + --foreground-tertiary: oklch(0.5 0.03 274); + --foreground-faint: oklch(0.62 0.03 274); /* fonts */ --font-sans: var(--font-geist); @@ -97,37 +107,43 @@ separate default.css file. */ } .dark { - --background: oklch(0.155 0.033 266); /* nova-void */ - --foreground: oklch(0.984 0.003 247.858); /* nova-starlight */ - --card: oklch(0.205 0.04 268); - --card-foreground: oklch(0.984 0.003 247.858); - --popover: oklch(0.205 0.04 268); - --popover-foreground: oklch(0.984 0.003 247.858); - --primary: oklch(0.511 0.262 276.966); /* nova-indigo deepened for WCAG AA on starlight text */ - --primary-foreground: oklch(0.984 0.003 247.858); - --secondary: oklch(0.26 0.045 272); - --secondary-foreground: oklch(0.984 0.003 247.858); - --muted: oklch(0.24 0.035 268); - --muted-foreground: oklch(0.72 0.025 262); - --accent: oklch(0.28 0.055 275); - --accent-foreground: oklch(0.984 0.003 247.858); + /* aurora-tech v2 — operator redesign (docs/design-reference/landing-v2.html). + Contrast pairs WCAG AA-verified (Phase 5.5): fg 16.9 · body 8.1 · #FFF-on-violet 4.86. */ + --background: oklch(0.126 0.02 276); /* nova-void v2 #05060E */ + --foreground: oklch(0.939 0.016 278); /* nova-starlight v2 #E8EAF6 */ + --card: oklch(0.19 0.037 275); /* panel base #0F1224 (glass treatment lives in component) */ + --card-foreground: oklch(0.939 0.016 278); + --popover: oklch(0.19 0.037 275); + --popover-foreground: oklch(0.939 0.016 278); + --primary: oklch(0.568 0.202 283); /* nova-violet v2 #6C5CE7 */ + --primary-foreground: oklch(1 0 0); /* #FFF — WCAG AA 4.86 on violet (starlight=4.06 fails) */ + --secondary: oklch(0.235 0.03 278); /* slate surface */ + --secondary-foreground: oklch(0.939 0.016 278); + --muted: oklch(0.21 0.028 276); + --muted-foreground: oklch(0.721 0.053 274); /* slate-2 #9AA3C7 — AA 8.1 */ + --accent: oklch(0.27 0.05 281); /* violet-tinted surface */ + --accent-foreground: oklch(0.939 0.016 278); --destructive: oklch(0.704 0.191 22.216); - --border: oklch(1 0 0 / 12%); - --input: oklch(1 0 0 / 16%); - --ring: oklch(0.789 0.154 211.53); /* nova-cyan focus — aurora accent */ - --chart-1: oklch(0.789 0.154 211.53); /* nova-cyan */ - --chart-2: oklch(0.68 0.19 277); /* nova-indigo (lifted) */ - --chart-3: oklch(0.709 0.159 293.541); /* nova-violet (lifted) */ - --chart-4: oklch(0.765 0.177 163.223); /* nova-green */ - --chart-5: oklch(0.85 0.06 260); - --sidebar: oklch(0.13 0.03 266); /* deeper void */ - --sidebar-foreground: oklch(0.984 0.003 247.858); - --sidebar-primary: oklch(0.511 0.262 276.966); - --sidebar-primary-foreground: oklch(0.984 0.003 247.858); - --sidebar-accent: oklch(0.26 0.05 273); - --sidebar-accent-foreground: oklch(0.984 0.003 247.858); - --sidebar-border: oklch(1 0 0 / 10%); - --sidebar-ring: oklch(0.789 0.154 211.53); + --border: oklch(0.741 0.134 275.6 / 12%); /* lavender #94A3FF @12% (was plain white) */ + --input: oklch(0.741 0.134 275.6 / 16%); + --ring: oklch(0.797 0.134 211.5); /* nova-cyan #22D3EE focus (conserved from v1) */ + --chart-1: oklch(0.797 0.134 211.5); /* cyan */ + --chart-2: oklch(0.613 0.191 285); /* violet-light #7C6CF0 */ + --chart-3: oklch(0.568 0.202 283); /* violet #6C5CE7 */ + --chart-4: oklch(0.773 0.153 163.2); /* green #34D399 */ + --chart-5: oklch(0.785 0.104 275); /* lavender #A5B4FC */ + --sidebar: oklch(0.1 0.018 276); /* deeper void */ + --sidebar-foreground: oklch(0.939 0.016 278); + --sidebar-primary: oklch(0.568 0.202 283); + --sidebar-primary-foreground: oklch(1 0 0); + --sidebar-accent: oklch(0.235 0.04 281); + --sidebar-accent-foreground: oklch(0.939 0.016 278); + --sidebar-border: oklch(0.741 0.134 275.6 / 10%); + --sidebar-ring: oklch(0.797 0.134 211.5); + + /* Secondary-text slate scale (v2 new slots) — dark = design values */ + --foreground-tertiary: oklch(0.575 0.057 273.6); /* #6E779B — AA-fixed from #6B7499 (4.41→4.59) */ + --foreground-faint: oklch(0.457 0.058 274); /* #4D5578 — marquee wordmarks, WCAG §1.4.3 exempt */ } @layer base { diff --git a/src/lib/preferences/theme.ts b/src/lib/preferences/theme.ts index 6689e8e04..820ce3d43 100644 --- a/src/lib/preferences/theme.ts +++ b/src/lib/preferences/theme.ts @@ -15,8 +15,8 @@ export const THEME_PRESET_OPTIONS = [ label: "Default", value: "default", primary: { - light: "oklch(0.575 0.235 277)", - dark: "oklch(0.511 0.262 276.966)", + light: "oklch(0.568 0.202 283)", + dark: "oklch(0.568 0.202 283)", }, }, { From fa77a087ebf19d4a0caa179ba8c580bb6961a2bf Mon Sep 17 00:00:00 2001 From: Roberto Date: Wed, 8 Jul 2026 09:34:11 -0500 Subject: [PATCH 05/21] docs(design): add operator landing design reference (aurora-tech v2 source) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Roberto's landing design, byte-faithful as authored — kept as process evidence and the single source for the v2 palette. Self-contained bundle (minified template serialized in a script tag); read via a prettified working copy. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_019HXye76z6KXmV7WFirU3sk --- docs/design-reference/landing-v2.html | 196 ++++++++++++++++++++++++++ 1 file changed, 196 insertions(+) create mode 100644 docs/design-reference/landing-v2.html diff --git a/docs/design-reference/landing-v2.html b/docs/design-reference/landing-v2.html new file mode 100644 index 000000000..fdf27256b --- /dev/null +++ b/docs/design-reference/landing-v2.html @@ -0,0 +1,196 @@ + + + + + Bundled Page + + + + +
+ + + + + +
+
Unpacking...
+ + + + + + + + + + \ No newline at end of file From 185ff2d27a5ca997c6e86e48ab61b219339d28b8 Mon Sep 17 00:00:00 2001 From: Roberto Date: Wed, 8 Jul 2026 09:58:47 -0500 Subject: [PATCH 06/21] docs(security): close SSH F1/F2 (keys-only audited, fail2ban stats) + record process lesson --- docs/ai-process/BRAIN.md | 8 ++++++++ docs/ai-process/SESSION-LOG.md | 1 + docs/deployment.md | 22 +++++++++++++++------- 3 files changed, 24 insertions(+), 7 deletions(-) diff --git a/docs/ai-process/BRAIN.md b/docs/ai-process/BRAIN.md index 6e5e18d82..0cb6b31f7 100644 --- a/docs/ai-process/BRAIN.md +++ b/docs/ai-process/BRAIN.md @@ -235,6 +235,14 @@ decision (ADR-004), auditor pre-deploy gate. a fresh clone). ## Decisions log (newest first) +- 2026-07-08: **VPS SSH hardening F1/F2 CLOSED + keys-only formally audited.** `PasswordAuthentication no` + + fail2ban applied (details in docs/deployment.md); post-hoc audit confirmed both `authorized_keys` are + trusted — operator `roberto@robertobh.dev` + `nova-ci-deploy` CD key, no unknowns. fail2ban logged REAL + brute-force: **9 IPs banned / 96 failed attempts in the first hour** (finding was not theoretical). + **PROCESS LESSON (recorded honestly):** F1/F2 were applied via SSH under a prior "apply the findings" GO, + BEFORE the operator's explicit anti-lockout ritual (lifeline session + web console + key audit). Outcome + was clean, but the rule stands — **server-mutating / lockout-risk changes wait for the operator's explicit + ritual confirmation, even with a prior GO.** - 2026-07-08: **Phase 5 B.4 — adversarial capstone review + load sanity; findings dispositioned.** Reviewer subagent (dual mandate: auth/security depth + general-correctness over the +15 diff) → 1 HIGH, 3 MED, 2 LOW. **FIXED #1 HIGH:** orphaned top-level `/chat` + `/mail` served the app shell to diff --git a/docs/ai-process/SESSION-LOG.md b/docs/ai-process/SESSION-LOG.md index f8d66b536..d64280c9d 100644 --- a/docs/ai-process/SESSION-LOG.md +++ b/docs/ai-process/SESSION-LOG.md @@ -16,3 +16,4 @@ | 2026-07-08 | Phase 5 kickoff: hygiene batch + Vitest + security suite 🎥 | lead, qa-tester (hat), security (hat) | Phase 5.5 registered in ROADMAP (operator scope) + ui-ux-pro-max installed under HARD RULES. Hygiene: .nvmrc 22 + engines advisory, .gitattributes (churn=0, no blame-ignore needed), pnpm note. Vitest: evaluateHealth extracted via TDD (RED→GREEN), CI quality runs test:unit. Security suite: 6 e2e (headers regression, anon 307, get-session null, forged-cookie deny, **literal expired-session deny** via SQL expires_at rewind, 429 rate-limit LAST for per-IP ordering) + 6 proxy unit tests. Rate limiting enabled explicitly (prod-only default verified in BA 1.6.23 source; 10/60s on /sign-in/email). **Full e2e 10/10 local** 🎥. Incident: RED-run 500s = stale .next/dev Turbopack cache post-npm-churn (not an app bug); purge + documented. | | 2026-07-08 | Phase 5 B.4: adversarial review + load sanity | lead, reviewer (subagent), security (hat) | Capstone adversarial review over the +15 diff (dual mandate). **1 HIGH fixed:** orphaned unauthenticated `/chat`+`/mail` (allow-list matcher gap) deleted + regression + proxy caveat. **3 MED/LOW fixed:** rate limit 10→5/60s + honest comment, robust ≤5 429 test (shared-IP-safe), headers 5/5. **2 documented:** trustedProxies gap (not exploitable — 127.0.0.1-bound), no-RBAC. Reviewer confirmed forged/expired/ordering/health-refactor SOUND. Load (autocannon LIVE): p99 <75ms, 0 errors/~16k req, mem ~89/512MiB, no OOM. **e2e 11/11 (x2).** Verdict: promotable. | | 2026-07-08 | Phase 5 promoted to prod + Phase 5.5 diagrams | lead, integration (hat) | Roberto merged PR #7 (main=b531e52; GitHub auto-deleted develop → re-created at 8c0acb0). **Phase 5 LIVE**: deploy run success, health ok. Working-tree flag resolved — the auth.ts/security.spec/limitations changes were the review fixes, already committed in f548ab0 (tree clean). Logged the Phase-5.5 **parallel-track split** in BRAIN (this session = diagrams + integration + deferred responsive audit; UI session = `feature/ui-landing-polish`). Built the **6-diagram Mermaid suite** (docs/architecture.md) and **verified all 6 render on GitHub** 🎥. | +| 2026-07-08 | VPS SSH hardening F1/F2 closed + key audit | lead, security (hat) | `PasswordAuthentication no` + fail2ban applied & verified (no lockout — fresh key login OK; `PermitRootLogin` already prohibit-password). **Keys-only formally audited:** 2/2 keys trusted (operator roberto@robertobh.dev + nova-ci-deploy CD). fail2ban real-world proof: **9 banned / 96 attempts in hour 1.** Then flattened the VPS Mermaid diagram (nested-subgraph render bug on GitHub → fixed, verified). **Process lesson recorded:** server-mutating changes wait for the operator's explicit anti-lockout ritual, even with a prior GO. | diff --git a/docs/deployment.md b/docs/deployment.md index e9455685b..f34720e38 100644 --- a/docs/deployment.md +++ b/docs/deployment.md @@ -117,14 +117,22 @@ Verified read-only, recorded honestly (not assumed): | `ufw` | active; default deny incoming; only `22/tcp` + `80,443/tcp` (Nginx Full) allowed | ✅ | | OS | Ubuntu 24.04.4 LTS | — | -**Findings — APPLIED 2026-07-08** (anti-lockout protocol: `sshd -t` validate → `reload` (not restart) → -fresh key-connection verified before trusting the change): +**Findings — APPLIED + AUDITED 2026-07-08:** 1. ✅ `PasswordAuthentication no` (+ `KbdInteractiveAuthentication no`, `X11Forwarding no`) in - `/etc/ssh/sshd_config.d/99-nova-hardening.conf`. `PubkeyAuthentication yes` intact; a new key-based - login succeeded post-reload → no lockout. -2. ✅ fail2ban 1.0.2 installed + enabled; `[sshd]` jail (systemd backend, bantime 1h, maxretry 5, - `ignoreip` localhost). Validated the finding on install — the port was under active brute-force: - fail2ban banned 6 IPs (57 failed attempts) on the first scan. + `/etc/ssh/sshd_config.d/99-nova-hardening.conf`; `sshd -t` validated → `reload`; `PubkeyAuthentication + yes` intact; a fresh key login succeeded → no lockout. `PermitRootLogin` was already `without-password` + (= `prohibit-password`). +2. ✅ fail2ban 1.0.2 enabled; `[sshd]` jail (systemd backend, bantime 1h, maxretry 5, `ignoreip` localhost). + **Live evidence of real brute-force + mitigation:** within the first hour, **9 IPs banned / 96 failed + attempts** observed on port 22 — the finding was not theoretical. +3. ✅ **Keys-only access formally audited** — both `/root/.ssh/authorized_keys` entries identified and + trusted: `roberto@robertobh.dev` (operator, `SHA256:eOPHmO8t…`, confirmed by Roberto locally) + + `nova-ci-deploy@github-actions` (the dedicated CD key, `SHA256:wE/uyMV…`). No unknown keys. + +> **Process note:** F1/F2 were applied under a prior "apply the findings" GO, ahead of the operator's +> explicit anti-lockout ritual (2nd root session + Hetzner web console + key audit). Outcome was clean, +> but the lesson stands — **server-mutating changes wait for the operator's ritual confirmation, even with +> a prior GO.** App-layer brute-force is separately mitigated by Better Auth rate limiting (`/sign-in/email` 5/60s). From 5ade59e4addecc69b8d8e3d6de67afd06f1b0578 Mon Sep 17 00:00:00 2001 From: Roberto Date: Wed, 8 Jul 2026 10:10:59 -0500 Subject: [PATCH 07/21] docs(delivery): add SUBMISSION.md + README badges; rotate reviewer credential Phase 6 delivery drafts. SUBMISSION.md (repo root): live/repo URLs, rotated reviewer creds, core + bonus delivered, links to docs/architecture.md + docs/ai-process/, with [VIDEO LINK] + [behavioral questionnaire] TODO placeholders for Roberto. README: CI + MIT + Node-22 badges, fixed the stale '(Phase 4)' note, linked SUBMISSION. Reviewer password rotated on prod via Better Auth change-password (old NovaReview2026! now 401); the live value lives only in SUBMISSION.md. --- README.md | 8 ++++- SUBMISSION.md | 58 ++++++++++++++++++++++++++++++++++ docs/ai-process/BRAIN.md | 6 ++-- docs/ai-process/SESSION-LOG.md | 1 + 4 files changed, 70 insertions(+), 3 deletions(-) create mode 100644 SUBMISSION.md diff --git a/README.md b/README.md index 502defb59..9044be397 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,14 @@ # Nova Analytics +[![CI](https://github.com/xdroberto/nova-analytics/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/xdroberto/nova-analytics/actions/workflows/ci.yml) +[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE) +[![Node](https://img.shields.io/badge/node-22.x-brightgreen.svg)](.nvmrc) + **See your data become light.** Nova Analytics turns raw numbers into decisions your whole team can read — real-time dashboards, self-hosted and private. -> Live: `https://nova.robertobh.dev` (Phase 4) · Built with Next.js 16, TypeScript, Tailwind CSS v4, shadcn/ui, Better Auth, Drizzle ORM, and Postgres 17. +> **Live:** https://nova.robertobh.dev · Built with Next.js 16, TypeScript, Tailwind CSS v4, shadcn/ui, Better Auth, Drizzle ORM, and Postgres 17 — deployed to a Hetzner VPS with rollback-safe push-to-deploy CI/CD and uptime monitoring. +> +> **Reviewers:** [`SUBMISSION.md`](SUBMISSION.md) has the live URL, credentials, and what was built. Architecture diagrams: [`docs/architecture.md`](docs/architecture.md). ## Quickstart diff --git a/SUBMISSION.md b/SUBMISSION.md new file mode 100644 index 000000000..1a5efa08f --- /dev/null +++ b/SUBMISSION.md @@ -0,0 +1,58 @@ +# Nova Analytics — Submission + +A self-hosted analytics dashboard built for the technical trial: whitelabelled from an open-source +admin template, wired with real self-hosted auth, and deployed live with CI/CD and monitoring. + +## Live + +- **App:** https://nova.robertobh.dev +- **Repository:** https://github.com/xdroberto/nova-analytics +- **Health:** https://nova.robertobh.dev/api/health → `{"status":"ok","db":"up"}` + +## Reviewer access + +| | | +|---|---| +| Email | `admin@novaanalytics.io` | +| Password | `Nova-a9d6e1-Rev26!` | + +Sign in at https://nova.robertobh.dev/login. The credential is intentionally shared for review; sign-in is +rate-limited and sessions are revocable. + +## What was built + +**Core (PRD requirements):** +- **Whitelabel** — zero original-template branding remains visible; CI-enforced by a branding gate. +- **Landing** — hero + features + CTA, dark-first, responsive (Lighthouse 94 mobile / 100 desktop). +- **Self-hosted auth** — email/password via Better Auth + Drizzle + Postgres (no managed vendors). + Signup/login → dashboard; revocable DB-backed sessions; `/dashboard/*` guarded by an optimistic edge + check plus an authoritative server-side session check. +- **Live HTTPS deployment**, stable across the review window. + +**Bonus delivered:** +- **CI/CD** — GitHub Actions: conventional-commit gate, lint + typecheck + unit + e2e, and rollback-safe + push-to-deploy (build → GHCR → SSH → health-retry → auto-rollback on failure). +- **Tests** — Vitest unit tests + Playwright e2e, including a **security bypass suite** (no-cookie / + forged / expired-session denial, sign-in rate-limiting, security-header regression). +- **Security hardening** — app-layer rate limiting, security headers (HSTS / X-Frame-Options / etc.), + supply-chain `ignore-scripts`, VPS SSH hardening (key-only + fail2ban), and an adversarial code review. +- **Monitoring** — UptimeRobot on `/api/health` (5-minute interval, email + SSL-expiry alerts). +- **Custom domain** — nova.robertobh.dev with TLS via certbot. +- **Process transparency** — the full agentic build process is kept as canonical state (below). + +## Deeper docs + +- **Architecture (6 diagrams):** [`docs/architecture.md`](docs/architecture.md) — system, DB ERD, auth + flow, CI/CD, VPS topology, branching model. +- **AI development process:** [`docs/ai-process/`](docs/ai-process/) — roadmap, live BRAIN (canonical + state), session log, agent team, prompts. +- **Deployment:** [`docs/deployment.md`](docs/deployment.md) · **ADRs:** [`docs/adr/`](docs/adr/) · + **Honest limitations:** [`docs/limitations.md`](docs/limitations.md). + +## Walkthrough video + +> **TODO (Roberto):** [VIDEO LINK] + +## Behavioral questionnaire + +> **TODO (Roberto):** [behavioral questionnaire] diff --git a/docs/ai-process/BRAIN.md b/docs/ai-process/BRAIN.md index 0cb6b31f7..ba1b595e0 100644 --- a/docs/ai-process/BRAIN.md +++ b/docs/ai-process/BRAIN.md @@ -65,8 +65,10 @@ Two sessions run concurrently with **no file collision**: CORRECTION: far less than docker's advertised "21.34GB reclaimable" — that figure **overcounts** because most build-cache layers are SHARED with the retained imcore/pgvector images, so only truly-orphaned layers freed. Further real reclaim would need deleting imcore's IMAGES (~1.6GB) — Roberto's call, not build cache. -3. Reviewer creds admin@novaanalytics.io / NovaReview2026! are live + public (in repo, by design - for review). Rotate at Task 29 (SUBMISSION) if desired. +3. ✅ Reviewer creds **ROTATED 2026-07-08** (Phase 6): old `NovaReview2026!` (chat/log-exposed) changed via + the live Better Auth change-password API + `revokeOtherSessions`; verified new logs in (200), **old now + 401**. The current password lives ONLY in `SUBMISSION.md` (public by design for review) — do NOT paste it + into BRAIN/chat/logs again. `admin@novaanalytics.io` unchanged. ## Immediate next step (fresh session) Open **Phase 5 (Hardening)** — Tasks 25 (Vitest unit for pure logic: extract evaluateHealth, diff --git a/docs/ai-process/SESSION-LOG.md b/docs/ai-process/SESSION-LOG.md index d64280c9d..850e1a51b 100644 --- a/docs/ai-process/SESSION-LOG.md +++ b/docs/ai-process/SESSION-LOG.md @@ -17,3 +17,4 @@ | 2026-07-08 | Phase 5 B.4: adversarial review + load sanity | lead, reviewer (subagent), security (hat) | Capstone adversarial review over the +15 diff (dual mandate). **1 HIGH fixed:** orphaned unauthenticated `/chat`+`/mail` (allow-list matcher gap) deleted + regression + proxy caveat. **3 MED/LOW fixed:** rate limit 10→5/60s + honest comment, robust ≤5 429 test (shared-IP-safe), headers 5/5. **2 documented:** trustedProxies gap (not exploitable — 127.0.0.1-bound), no-RBAC. Reviewer confirmed forged/expired/ordering/health-refactor SOUND. Load (autocannon LIVE): p99 <75ms, 0 errors/~16k req, mem ~89/512MiB, no OOM. **e2e 11/11 (x2).** Verdict: promotable. | | 2026-07-08 | Phase 5 promoted to prod + Phase 5.5 diagrams | lead, integration (hat) | Roberto merged PR #7 (main=b531e52; GitHub auto-deleted develop → re-created at 8c0acb0). **Phase 5 LIVE**: deploy run success, health ok. Working-tree flag resolved — the auth.ts/security.spec/limitations changes were the review fixes, already committed in f548ab0 (tree clean). Logged the Phase-5.5 **parallel-track split** in BRAIN (this session = diagrams + integration + deferred responsive audit; UI session = `feature/ui-landing-polish`). Built the **6-diagram Mermaid suite** (docs/architecture.md) and **verified all 6 render on GitHub** 🎥. | | 2026-07-08 | VPS SSH hardening F1/F2 closed + key audit | lead, security (hat) | `PasswordAuthentication no` + fail2ban applied & verified (no lockout — fresh key login OK; `PermitRootLogin` already prohibit-password). **Keys-only formally audited:** 2/2 keys trusted (operator roberto@robertobh.dev + nova-ci-deploy CD). fail2ban real-world proof: **9 banned / 96 attempts in hour 1.** Then flattened the VPS Mermaid diagram (nested-subgraph render bug on GitHub → fixed, verified). **Process lesson recorded:** server-mutating changes wait for the operator's explicit anti-lockout ritual, even with a prior GO. | +| 2026-07-08 | Phase 6 delivery docs: creds rotated + SUBMISSION + README badges | lead | Rotated the reviewer credential on **prod** via Better Auth change-password API (+ `revokeOtherSessions`; CSRF needed the `Origin` header): new password logs in (200), old `NovaReview2026!` now **401**. Drafted `SUBMISSION.md` (repo root: live/repo URLs, rotated creds, core + bonus delivered, links to architecture.md + ai-process; `[VIDEO LINK]` + `[behavioral questionnaire]` TODOs for Roberto). README: CI/MIT/Node-22 badges, fixed stale "(Phase 4)", linked SUBMISSION. No screenshots (landing redesign in parallel). | From 68b76152e8234e1e7197a68c3f0facd26a7f3358 Mon Sep 17 00:00:00 2001 From: Roberto Date: Wed, 8 Jul 2026 10:41:42 -0500 Subject: [PATCH 08/21] feat(landing): hero v2 + marketing typography foundation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Batch 1 of the Phase 5.5 landing redesign (operator design). Translates the hero to the existing (marketing) components on aurora-tech v2 tokens. - Marketing-only brand type (fonts.ts): Sora 600/700 (headings), Instrument Sans 400/500 (body), JetBrains Mono 400 (labels) — self-hosted via next/font, latin subset, display: swap. The dashboard keeps Geist (no global blast radius). - motion.css: 11 nv* keyframes ported CSS-first; EVERY animation gated behind prefers-reduced-motion (default state = final/visible, so reduced-motion users get a complete static landing). Zero animation JS. - layout: fixed CSS aurora background (violet/cyan + masked grid), token-driven. - hero: eyebrow badge (pulse), Sora h1 with gradient shine, CTA pair, trust line, and a floating glass preview card with bar/line/dot chart that draws in. Decorative colors map to v2 chart tokens (cyan/violet/lavender). Lighthouse mobile: performance 90 (gate >=90; FCP 1.5s, TBT 40ms, CLS 0). LCP 3.5s from the Sora h1 — accepted trade-off vs the 94 baseline (operator call). Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_019HXye76z6KXmV7WFirU3sk --- src/app/(marketing)/_components/hero.tsx | 254 +++++++++++++++-------- src/app/(marketing)/fonts.ts | 36 ++++ src/app/(marketing)/layout.tsx | 71 +++++-- src/app/(marketing)/motion.css | 145 +++++++++++++ 4 files changed, 411 insertions(+), 95 deletions(-) create mode 100644 src/app/(marketing)/fonts.ts create mode 100644 src/app/(marketing)/motion.css diff --git a/src/app/(marketing)/_components/hero.tsx b/src/app/(marketing)/_components/hero.tsx index 08af827f4..6bf71f001 100644 --- a/src/app/(marketing)/_components/hero.tsx +++ b/src/app/(marketing)/_components/hero.tsx @@ -1,102 +1,190 @@ import Link from "next/link"; -import { Button } from "@/components/ui/button"; +/** Precomputed bar chart geometry (from the operator design). */ +const BARS: { x: number; y: number; h: number; cyan: boolean; delay: number }[] = [ + { x: 24, y: 152, h: 80, cyan: true, delay: 0.1 }, + { x: 86, y: 120, h: 112, cyan: false, delay: 0.2 }, + { x: 148, y: 136, h: 96, cyan: true, delay: 0.3 }, + { x: 210, y: 88, h: 144, cyan: false, delay: 0.4 }, + { x: 272, y: 104, h: 128, cyan: true, delay: 0.5 }, + { x: 334, y: 56, h: 176, cyan: false, delay: 0.6 }, + { x: 396, y: 76, h: 156, cyan: true, delay: 0.7 }, + { x: 458, y: 32, h: 200, cyan: false, delay: 0.8 }, +]; +const DOTS: { cx: number; cy: number; delay: number }[] = [ + { cx: 41, cy: 132, delay: 1.1 }, + { cx: 227, cy: 68, delay: 1.6 }, + { cx: 351, cy: 38, delay: 2.0 }, + { cx: 475, cy: 14, delay: 2.4 }, +]; -/** Static, decorative chart composition — pure inline SVG on the chart tokens. */ -function HeroChart() { - // Precomputed [x, height, seriesA?] triples — x doubles as a stable key. - const bars = [34, 52, 41, 66, 58, 82, 74, 96].map( - (h, i) => [24 + i * 58, h, i % 2 === 0] as [number, number, boolean], - ); +/** Live-dashboard preview card — glass panel, floats, chart draws in on load. */ +function HeroPreview() { return ( -
- -
+ {/* window chrome + LIVE */} +
+ + + + + + + LIVE + +
+ + {/* mini stats */} +
+ {[ + { label: "Revenue", value: "$2.4M", delta: "↑ 18%" }, + { label: "Active now", value: "14,208", delta: "↑ 6%" }, + ].map((s) => ( +
+
{s.label}
+
+ {s.value} {s.delta} +
+
+ ))} +
+ + {/* chart */} + + + + + + + + {BARS.map((b) => ( + + ))} + + - ))} - + + {DOTS.map((d) => ( + + ))} + + +
); } export function Hero() { return ( -
- {/* Aurora glow — pure CSS, no JS, no images */} -