v22.x · Pre-launch audit hardening
- Accessibility: skip-to-content link rolled out on all 18 main pages; color contrast lifted to WCAG AA throughout.
- SEO + schema: FAQPage JSON-LD added to pricing (Google rich-snippet eligibility); real meta descriptions on all 12 help articles.
- Developer experience: OpenAPI 3.1 spec published at
/api/openapi.json; SDK status documented; RSS feed for this changelog. - 404 page: now has a search input (submits to /help.html) plus the standard skip-link.
- Public
/api/healthendpoint for external uptime monitors (BetterStack, Statuspage, UptimeRobot). - GDPR Article 17 hard-delete cron — 30-day grace window, then full subprocessor cleanup (Stripe customer delete + Resend audience remove).
- API key reverse index — authenticated API calls now O(1) lookup instead of O(N) scan. Old keys stop validating immediately on rotate.
- Audit log date-partitioned for efficient range queries from the admin panel.
- Terms of Service v1.2 — added sanctions / export-controls clause, DPA reference, Enterprise SLA definition (99.5% uptime + service credits), data-residency statement.
- Privacy policy v2.0 — full rewrite to GDPR / CCPA grade. Correct subprocessor list (Netlify Forms, Resend, Stripe, Netlify Blobs, CARTO, OSM, CDNJS, Google Fonts). Lawful basis per Article 6, data-subject rights workflow, retention schedules per data class, international transfer mechanism (SCCs), data-residency statement, automated decision-making disclosure.
- Team page — new /team.html with company governance, accountability framing, and security posture cross-links.
Organization+AboutPageJSON-LD. - Pricing reconciled across all surfaces — Enterprise consistently $999/mo in meta description, OG, Twitter, JSON-LD, and the visible card. Invalid
billingDurationSchema.org property replaced with validbillingPeriod. - Subscriber password storage migrated to scrypt (memory-hard, OWASP-recommended) with per-user salts. Legacy SHA-256 records re-hash transparently on next login or password change.
- Stripe webhook now deduplicates events by ID (7-day window) so retries are harmless; returns 500 on transient handler failure so Stripe retries instead of silently dropping events.
- /.well-known/security.txt published for responsible-disclosure reporters.
- Methodology version stamps reconciled — scoring spec v18.1 consistently referenced; deploy build versioned independently.
- Subscriber session tokens now HMAC-SHA256 signed (previously unsigned base64). Forged tokens cannot impersonate other users.
- Session cookies set
HttpOnly+Secure+SameSite=Strict— not readable by JavaScript, not sent on cross-site requests. - Rate limits added to subscriber login (5/15 min), password change (3/15 min), and account deletion (3/15 min) per (IP + email).
- Customer-facing endpoints hardened: billing portal requires verified session; checkout-session lookup requires correct Referer + 30-min Stripe-session age + per-IP rate limit; mass-mail endpoints (daily brief, onboarding drip) require a cron secret or admin/staff cookie.
/api/intel-feednow tier-shaped — unauthenticated callers get a 3-hotspot public preview; signed-in users get the full Pro-tier dataset.- Security-notification emails sent automatically on password change and account deletion.
- Daily brief email converted to light theme so it renders correctly in Gmail dark mode (previously Gmail's color inversion made the white wordmark invisible against the inverted dark background).
- The "View this snapshot" link at the bottom of every brief now resolves correctly to today's archived snapshot.
- Country Risk Assessment .docx file no longer triggers Word's "unreadable content" warning on open. Header logo unified to a single icon + wordmark + tagline image (140px wide), centered.
- Welcome email and Daily Brief logos consolidated.
- Live Atlas now signs you out after 30 minutes of inactivity, with a 1-minute warning toast.
- Welcome email gets a 200×200 AtlasRisks logo header.
- New: every country drill-down now offers a Download CRA (.docx) button (Pro+) that produces a branded Word document with the country's full risk profile, scoring breakdown, events of concern, and methodology citation.
- The All Countries "Watch" button now properly persists to the subscriber's watchlist.
- Top Movers Down row restored on Live Atlas; CRA download surfaced on table rows and watchlist chips.
- Source pool expanded with 24 new feeds: US State Department travel advisories, UN OCHA updates, CARICOM secretariat, plus additional government, NGO, and country-specific regional / alternative media sources.
- Total: 91 sources across 4 reliability tiers. Western-mainstream concentration reduced from 53% to ~32% of the pool.
- Pricing page annual toggle now persists through to the Stripe checkout flow. Pro Annual at $999/yr (~16% savings vs $99/mo × 12).
- Signup page splits "Sign in" vs "Create account" CTAs cleanly. Manage page shows "Switch to Annual" upgrade banner for monthly subscribers.
- Free-tier dashboard shows clear "locked" placeholders for Pro-only Top Movers and Country Comparison sections.
v20.x · The customer-experience line
- This page —
/changelog.html— pulls the version history into one public-facing log. Linked from every footer. pricing.htmlcompare table now includes the Custom Enterprise column (unlimited seats, federated workspaces, on-prem deployment, dedicated CSM, custom SLA, BYO SSO + audit export, founder direct line). Custom is no longer just a strip card under the grid.
One-click + Watch this country toggle inside every country drill-down. Signed-in subscribers no longer need to detour through Settings → Edit watchlist to track a country they're already looking at. Toggle flips to ✓ Watching when added; click again removes. Server enforces the per-tier seat cap (free 1 / paid 10 / team 25 / enterprise 75 / custom 999) and surfaces capped/truncated states inline.
v19.x · Revenue + product feature line
- New Country Comparison panel: pick 2-3 countries, see a hand-rolled SVG radar chart over the 10 v18 categories side-by-side, with a numerical category table beneath. Useful for "same headline, different operational profile" analysis.
- Refresh button enlarged ~50%, relabeled
↻ Refresh data, idle glow pulse, spinning ↻ during fetch. - Sign in via the Settings drawer → Live Atlas auto-pulls fresh
/api/intel-feed. No manual refresh needed.
- The "Atlas — Online" dot pulses green when pipeline data is fresh (<30 min), amber slow-pulse for 30 min–6 h, amber static for 6–12 h, red static for 12+ h. Tier flip on every fetch.
/dashboard.html#IRNnow auto-opens Iran's drill-down + scrolls it into view. Closes the v18.2 audit's search-to-map disconnect — operators can bookmark, share, or link directly to any country panel.
- Fixed "Last Update 9h ago ago" duplicate (HTML literal collided with
fmtAgo()output). - Disabled the legacy v9 simulator that was racing the v10 adapter on
#last-sweep(visible flicker between values). - Status strip "Sources / Countries / Methodology" numbers now live-bound to the payload instead of stale hardcoded "41 / 193 / v1.1".
- Removed legacy "TSG PIPELINE" label (AtlasRisks has been the canonical OSINT host since v16.1).
GET /api/v1/export?format=csv|json downloads today's full hotspot state — one row per country with the ten-category breakdown flattened to 40 columns. CSV opens cleanly in Excel; JSON is the pretty-printed full payload. "📥 Download today's data" button in the Settings panel for browser users; same Bearer auth + rate limit as the other v1 endpoints.
Settings drawer in the Live Atlas now includes an Account section: name, email, account-type pill (paid/trial/free), status, watchlist preview, API-key fingerprint. Five action buttons — Change password, Edit watchlist, Manage billing, Sign out, Delete account. New endpoints: /api/auth/me, /api/auth/change-password, /api/auth/delete-account, /api/subscriber-set-watchlist.
- Honors
prefers-reduced-motionsite-wide (animations + transitions disabled when requested). - Global
:focus-visibleoutline — every interactive element shows a clear focus ring for keyboard users. - Skip-to-content link as the first body element on the long pages (dashboard, admin).
- Static audit memo published — addresses the v18.2 deferred a11y item on every dimension except the ones that require a live browser (Lighthouse, axe DevTools).
- API access (sold on pricing.html since v16.3) now actually exists:
GET /api/v1/hotspots+GET /api/v1/country/:isowith Bearer auth, per-key rate limit (Pro 50/hr · Team 100/hr · Enterprise 250/hr · Custom 1000/hr). Public docs at /api.html. - Admin can issue / rotate / revoke per-subscriber API keys. Plaintext shown once; only the SHA-256 hash persists.
- 4-email onboarding drip: day 0 welcome → day 2 tutorial → day 7 case study → day 12 trial-ending or upgrade nudge. Cron at 13:00 UTC daily.
Pricing has sold "Score-delta alerts" since v16.3 — v19.2 ships them. After each pipeline refresh, the engine scans every subscriber's watchlist; any country with |Δactive| ≥ threshold (paid 10, trial 15) triggers a single-country alert email within minutes. Per-(email, ISO, day) dedup. Daily cap 10/subscriber. Opt-out flag honored.
POST /api/checkoutcreates a real Stripe Checkout Session for the Pro tier with a 15-day trial. The webhook (HMAC-SHA256 signature verified) upgrades the subscriber fromsource=trialtopaidon completion./manage.htmlpage + Settings panel button route subscribers to the Stripe Billing Portal for self-service cancellation / plan change / payment method.
v18.x · Scoring methodology rebuild
Three sub-rounds closing the Western-press concentration the v18.2 audit flagged:
- Sub-round 1 — 9 regional anchors: Premium Times Nigeria, East African, Mail & Guardian SA, Times of India, Nikkei Asia, SCMP, Folha de São Paulo, El Universal, Clarín.
- Sub-round 2 — wire + state-affiliated: AFP, TASS, Xinhua, PressTV, Anadolu. State-affiliated outlets carry the
stateAffiliated:trueflag and weight 0.30 — corroborate but never dominate scoring. - Sub-round 3 — economic + specialist + governmental: IMF Press Center, Federal Reserve, ECB, FRED Blog, Al-Monitor, Defense One, EU Council Press, Japan MOFA travel advice.
Western-mainstream concentration dropped from 53% → ≈32%. Economic-category active signal is now live (rate decisions, IMF Article IV) instead of OFAC-only.
Per-event scoring formula: event_score = severity × source_reliability × recency × frequency × business_impact × locus_factor. Asymptotic compression replaces the hard 100-cap: values above 85 squash toward 100 along an exponential curve and never quite reach it — so a tactical-nuclear-exchange day in Russia can still produce a meaningfully higher headline than a quiet day. Two-decimal display so subscribers can see real precision at the top of the band.
Risk scoring decomposed into ten weighted categories — security, political, civil unrest, crime, economic, regulatory, infrastructure, cyber, health, advisory. Per-country profile assignments (state_collapse_war / peer_war_invader / war_defender / fragile_authoritarian / criminal_violence / regional_tension / stable_democracy / default) drive structurally honest baselines. Russia and Israel can sit at the same headline GGRI but have completely different top-3 category profiles — the methodology now communicates that.
Tier 1 audit across 12 dimensions: storage, scale, broken links, code redundancy, source diversity, per-country source attribution, usability, efficiency, SEO, security, pricing. 13 numbered recommendations R1-R13. Two strategic gaps surfaced: source diversity (closed in v18.7-v18.9) and pricing positioning (addressed in v18.4 Option B). The audit memo is available on request.
v17.x · Hybrid headline + live-merger
For CRIT countries (baseline ≥ 75): headline = baseline + (active − 50) × 0.25 — modulates Sudan/Ukraine/Russia/Israel headlines so they actually move on the day's news. For non-CRIT countries: max(static, active) — a Sweden terror incident still escalates the headline appropriately. Both clamps come before the v18.1 asymptotic compression.
Every country score is now two numbers: risk_static (curated structural baseline, slow-moving) and risk_active (pure event-driven, fast). Headline is derived from both. Surfaces in dashboard + daily brief + admin diagnostics so subscribers can see whether today's GGRI is driven by structural fragility or news intensity.
Closes the v16.6 audit P0/P1 items: pricing typos corrected to $99/mo, Pro CTA repointed from broken /checkout → /signup.html, "41 sources" → "45 sources" across 6 pages. Daily brief email gains RFC 2369 + RFC 8058 List-Unsubscribe headers for Gmail one-click unsubscribe (also biases out of Promotions tab).
v16.x · Country-relevance + admin layer
Full CRUD over the daily-brief subscriber list inside /admin — add/remove subscribers, source pill (paid/trial/free), test-send button, last-loaded timestamp.
Word-boundary regex country matching so "Indiana" no longer matches India, "Iranian" no longer matches Iran, "Chinatown" no longer matches China. Drill-down events also now filter to locus/actor roles only — passing-mention items get dropped from the per-country panel.
Every event now carries a per-country role: locus (the country where the event occurred), actor (the country that did the thing), mention (passing reference). Only locus events contribute meaningfully to a country's score. Stops "US Senate condemns X" from counting as a US event when it's actually a country-X event.
Earlier versions (v9 – v15) cover the initial methodology bring-up, OSINT pipeline construction (10-category SOURCE_REGISTRY, 21 event types, recency decay, country-attribution confidence), and the edge-function admin gate. Available on request to hello@atlasrisks.com.