public: landing + pricing + legal pages, apex-ready, lawyer-reviewed
Adds the unauthenticated surface that's needed to invite outsiders:
- Landing (/) — dual-purpose root: dashboard for logged-in users,
landing for everyone else. New maybe_current_user soft-auth helper
in app/auth.py supports it without disturbing the per-route
require_token deps on /news, /log, /upload, /settings.
- About, Pricing, Disclaimer, Terms, Privacy — own router
(app/routers/public.py), no auth dep, shared public_base layout
(brand link, thin nav, footer with legal links + ICO ref + date).
- Editorial positioning: news aggregator with a macro brain; tagline
"Understand markets. Don't gamble on them."; anti-trading-as-gambling
stance carried through About and Landing.
Legal pass following an independent lawyer-style review:
- Privacy: explicit UK-GDPR Art. 6 lawful-basis section; Art. 22
automated-decision line; explicit consent for sessionStorage sync
key (PECR); 30-day IP-log retention; Art. 21 objection right;
Children clause; Art. 33/34 breach-notification clause;
international-transfer mechanism (IDTA + UK Addendum). ICO
registration ZC098928 surfaced at the top.
- Pricing: paid-card AI-portfolio-analysis bullet rewritten to remove
advice-shaped wording ("what would invalidate the posture" gone);
added italic carve-out citing FSMA / FCA COBS.
- Disclaimer: separate EU/EEA carve-out + MAR 596/2014 Art. 3(1)(34)
commentator safe-harbour; "qualifies the Terms" line; hallucination
wording fixed.
- Terms: cl.4 explicit AI-training prohibition + harassment line;
cl.5 CCR 2013 14-day cancellation; cl.7 softened AI copyright
claim under CDPA s.9(3) ambiguity; cl.8 proportionate suspension +
pro-rata refund for paid users; cl.10 CRA 2015 Pt 1 statutory-rights
carve-out from the liability cap; cl.11 right to close account on
material change; cl.12 non-exclusive jurisdiction + UK consumer
local courts.
Code-side enforcement of the Privacy claim:
- openrouter.py: outbound OpenRouter calls now carry
X-OR-Allow-Training: false. DeepSeek doesn't expose a per-request
flag; the Privacy page discloses this caveat verbatim.
Apex domain prep:
- branding.APP_URL flipped to https://read.markets (was app.). DNS for
the apex already resolves; pending operator NPM step is a cert that
covers the bare apex + a 301 from app.read.markets. No hard-coded
subdomain references remain in code (verified with grep).
Nav + chrome:
- app dropdown gains Pricing / Terms / Privacy / Disclaimer links.
- login.html gains a small legal-links footer for the
highest-leverage moment to surface them.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
6f9a710726
commit
f1903e1e61
17 changed files with 1436 additions and 10 deletions
311
app/templates/privacy.html
Normal file
311
app/templates/privacy.html
Normal file
|
|
@ -0,0 +1,311 @@
|
|||
{% extends "public_base.html" %}
|
||||
{% block title %}{{ BRAND_NAME }} · Privacy{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
|
||||
<section class="public-section">
|
||||
<h1 class="public-section__head">Privacy notice</h1>
|
||||
<p style="color: var(--muted); font-size: 13px;">
|
||||
Last updated: 2026-05-24. The operator (data controller) is
|
||||
{{ LEGAL_OPERATOR }}, {{ OPERATOR_JURISDICTION }}. Registered with
|
||||
the UK Information Commissioner’s Office under reference
|
||||
<strong>ZC098928</strong>. Questions:
|
||||
<a href="mailto:{{ OPERATOR_EMAIL }}">{{ OPERATOR_EMAIL }}</a>.
|
||||
</p>
|
||||
<p>
|
||||
This page describes exactly what we collect, what we don’t,
|
||||
where it lives, and how long we keep it. It is written from the code,
|
||||
not from a template — every claim corresponds to an explicit
|
||||
code path we’re happy to point a reviewer at.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section class="public-section">
|
||||
<h2 class="public-section__head">What we collect</h2>
|
||||
<ul>
|
||||
<li>
|
||||
<strong>Your email address</strong>, when you sign in. We use it
|
||||
only to send one-time login codes.
|
||||
</li>
|
||||
<li>
|
||||
<strong>An argon2 hash of each login code</strong>, plus expiry
|
||||
and attempt counts. The plaintext code is sent to your inbox and
|
||||
never written to disk on our side.
|
||||
</li>
|
||||
<li>
|
||||
<strong>A signed session cookie</strong> after you verify a code.
|
||||
It contains your user id only and is signed so we can detect
|
||||
tampering. Cookie is marked Secure and HttpOnly.
|
||||
</li>
|
||||
<li>
|
||||
<strong>Anonymous ticker universe</strong>: when you upload a
|
||||
portfolio CSV we record which Yahoo tickers appear, with
|
||||
<em>no link</em> to your account. The same row would exist whether
|
||||
any specific user holds the ticker or not — once a ticker is in
|
||||
the universe, the row carries no signal as to whose import added it.
|
||||
</li>
|
||||
<li>
|
||||
<strong>If you opt in to encrypted cloud sync</strong>: an opaque
|
||||
blob of bytes per user. The blob is your portfolio, encrypted in
|
||||
your browser with a PIN you choose, then wrapped a second time on
|
||||
the server with a key only the server holds. We can’t decrypt
|
||||
the blob to plaintext without your PIN, and we can’t recover
|
||||
your PIN if you forget it. By enabling cloud sync you give your
|
||||
consent (UK-GDPR Art. 6(1)(a)) to this processing; you can
|
||||
withdraw consent at any time by disabling sync in Settings, which
|
||||
also removes the server-side blob.
|
||||
</li>
|
||||
<li>
|
||||
<strong>Anonymised cost ledger</strong> of AI calls (model, tokens,
|
||||
cost). No portfolio or personal data is attached to ledger rows.
|
||||
</li>
|
||||
<li>
|
||||
<strong>Referral linkage</strong>: if you signed up via an invite
|
||||
link, we record which existing user’s code you used so we can
|
||||
apply the agreed referral credit later.
|
||||
</li>
|
||||
<li>
|
||||
<strong>Job-run telemetry</strong>: success/failure timestamps for
|
||||
the scheduled jobs that fetch market data and generate AI reads.
|
||||
No user identifiers are attached.
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section class="public-section">
|
||||
<h2 class="public-section__head">What we don’t collect</h2>
|
||||
<ul>
|
||||
<li>
|
||||
<strong>Your portfolio holdings as plaintext on the server.</strong>
|
||||
Parsed pies are returned to your browser and kept in
|
||||
<code>localStorage</code>. The server’s view is the anonymous
|
||||
ticker universe described above.
|
||||
</li>
|
||||
<li>
|
||||
<strong>Third-party analytics or ad cookies.</strong> No Google
|
||||
Analytics, no Hotjar, no Segment, no Facebook pixel, no LinkedIn
|
||||
tag. (You can verify by viewing-source on any page.)
|
||||
</li>
|
||||
<li>
|
||||
<strong>Browser fingerprints.</strong>
|
||||
</li>
|
||||
<li>
|
||||
<strong>IP-address joins to your user identity.</strong> IP
|
||||
addresses are processed transiently by the reverse proxy for
|
||||
security and access logging, retained for up to 30 days, and not
|
||||
linked to your account record.
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section class="public-section">
|
||||
<h2 class="public-section__head">Lawful basis (UK-GDPR Art. 6)</h2>
|
||||
<p>We rely on the following lawful bases:</p>
|
||||
<ul>
|
||||
<li>
|
||||
<strong>Performance of a contract</strong> (Art. 6(1)(b)) — for
|
||||
operating your account, the sign-in flow, paid features, and the
|
||||
mechanics of encrypted cloud sync.
|
||||
</li>
|
||||
<li>
|
||||
<strong>Legitimate interests</strong> (Art. 6(1)(f)) — for the
|
||||
anonymous ticker universe, the anonymised cost ledger, job-run
|
||||
telemetry, and reverse-proxy access logs. Our interest is the
|
||||
secure, abuse-resistant, cost-controlled operation of a free
|
||||
public service, balanced against the minimal and de-identified
|
||||
nature of the data.
|
||||
</li>
|
||||
<li>
|
||||
<strong>Consent</strong> (Art. 6(1)(a)) — where you opt in to
|
||||
encrypted cloud sync (and the related caching of a derived
|
||||
encryption key in your browser’s <code>sessionStorage</code>).
|
||||
You can withdraw consent at any time by disabling sync in
|
||||
Settings; the cached key is cleared and the server-side blob is
|
||||
removed.
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section class="public-section">
|
||||
<h2 class="public-section__head">Automated decisions and profiling</h2>
|
||||
<p>
|
||||
The Service does not make decisions about you that produce legal or
|
||||
similarly significant effects in an automated way (UK-GDPR Art. 22).
|
||||
The AI portfolio analysis is editorial commentary on the holdings
|
||||
you upload; it does not approve, reject or rank you, and you remain
|
||||
the sole decision-maker about anything in your account.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section class="public-section">
|
||||
<h2 class="public-section__head">Cookies and local storage</h2>
|
||||
<ul>
|
||||
<li>
|
||||
<strong>Session cookie</strong> — strictly necessary for keeping
|
||||
you signed in (PECR reg. 6(4)). No prior consent required.
|
||||
</li>
|
||||
<li>
|
||||
<strong>Local preferences</strong> — your chosen theme (light /
|
||||
dark) and reading level (Novice / Intermediate) are stored in
|
||||
<code>localStorage</code> on your device. They never leave the
|
||||
browser.
|
||||
</li>
|
||||
<li>
|
||||
<strong>Local portfolio + cached sync key</strong> — parsed pies
|
||||
live in <code>localStorage</code> on your device. If you enable
|
||||
cloud sync, the derived encryption key is cached in
|
||||
<code>sessionStorage</code> so you don’t have to re-enter
|
||||
your PIN on every navigation. This caching is performed only with
|
||||
your consent (given when you enable sync); it is cleared when you
|
||||
close the tab or disable sync.
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section class="public-section">
|
||||
<h2 class="public-section__head">Where the data lives, and international transfers</h2>
|
||||
<p>
|
||||
The server runs in {{ OPERATOR_JURISDICTION }}. Data is stored in a
|
||||
MariaDB database on the same host, backed up locally.
|
||||
</p>
|
||||
<p>
|
||||
Two flows can take personal data outside the UK:
|
||||
</p>
|
||||
<ul>
|
||||
<li>
|
||||
<strong>SMTP</strong> for sending one-time login codes. Operator-hosted,
|
||||
currently inside the UK; if that changes we will update this notice.
|
||||
</li>
|
||||
<li>
|
||||
<strong>AI provider calls</strong> for the strategic log, indicator
|
||||
summaries, and (paid) portfolio analysis. Where the provider sits
|
||||
outside the UK, we rely on the UK International Data Transfer
|
||||
Agreement (IDTA) / the UK Addendum to the EU Standard Contractual
|
||||
Clauses where no adequacy decision applies. Each outbound request
|
||||
carries an explicit no-training opt-out header
|
||||
(<code>X-OR-Allow-Training: false</code> on OpenRouter); see the
|
||||
Third parties section below for the caveats.
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section class="public-section">
|
||||
<h2 class="public-section__head">Retention</h2>
|
||||
<ul>
|
||||
<li>
|
||||
<strong>Login codes</strong>: expire after a few minutes; row
|
||||
remains briefly to enforce single-use, then is purged.
|
||||
</li>
|
||||
<li>
|
||||
<strong>Session cookies</strong>: expire automatically; you can
|
||||
sign out at any time to revoke.
|
||||
</li>
|
||||
<li>
|
||||
<strong>Ticker universe</strong>: rows untouched for 60 days are
|
||||
evicted by a nightly job. Active tickers remain.
|
||||
</li>
|
||||
<li>
|
||||
<strong>Encrypted portfolio blob</strong>: kept until you disable
|
||||
cloud sync (one click in Settings) or delete your account. We hold
|
||||
one row per user; new uploads overwrite the previous blob.
|
||||
</li>
|
||||
<li>
|
||||
<strong>Account</strong>: held until you ask us to delete it.
|
||||
Email <a href="mailto:{{ OPERATOR_EMAIL }}">{{ OPERATOR_EMAIL }}</a>.
|
||||
</li>
|
||||
<li>
|
||||
<strong>Cost ledger and job telemetry</strong>: retained for
|
||||
operational accounting; no personal data attached.
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section class="public-section">
|
||||
<h2 class="public-section__head">Third parties</h2>
|
||||
<ul>
|
||||
<li>
|
||||
<strong>SMTP provider</strong>: an operator-hosted Mailu server
|
||||
sends the one-time login codes. The provider sees your email
|
||||
address and the code body (the code itself).
|
||||
</li>
|
||||
<li>
|
||||
<strong>AI provider(s)</strong>: DeepSeek (primary) with OpenRouter
|
||||
as a fallback. They see the prompt for the strategic log, the
|
||||
indicator summaries, and the portfolio analysis call — which
|
||||
contains your holdings only when you press
|
||||
“Generate AI analysis” on a paid plan, and only for the
|
||||
duration of that single call. The portfolio analysis output is not
|
||||
persisted on the server.
|
||||
<br>
|
||||
<strong>No-training opt-out.</strong> Every OpenRouter request
|
||||
carries the <code>X-OR-Allow-Training: false</code> header, which
|
||||
signals to OpenRouter and any compatible upstream that the prompt
|
||||
must not be used to train or improve models. DeepSeek does not
|
||||
currently expose a per-request opt-out; if you do not want your
|
||||
holdings to leave our server at all, do not use the AI portfolio
|
||||
analysis feature. We do not control retention or training policies
|
||||
on the provider side beyond the headers we set — the provider’s
|
||||
own published data policy is the binding statement on that point.
|
||||
</li>
|
||||
<li>
|
||||
<strong>Market-data sources</strong>: Yahoo Finance and a small set
|
||||
of public RSS feeds. We request prices and headlines; we don’t
|
||||
send them any user identifier.
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section class="public-section">
|
||||
<h2 class="public-section__head">Your rights (UK-GDPR)</h2>
|
||||
<p>You have the right to:</p>
|
||||
<ul>
|
||||
<li>Ask what personal data we hold about you (Art. 15, right of access).</li>
|
||||
<li>Have inaccurate data corrected (Art. 16, rectification).</li>
|
||||
<li>Have your account and associated data deleted (Art. 17, erasure).</li>
|
||||
<li>Export the data you can recognise (Art. 20, portability): your
|
||||
email, any active encrypted blob, your referral linkage.</li>
|
||||
<li>Restrict processing (Art. 18).</li>
|
||||
<li>Object specifically to processing carried out on the basis of
|
||||
legitimate interests (Art. 21), including any direct marketing.</li>
|
||||
<li>Withdraw consent at any time where processing is based on
|
||||
consent (Art. 7(3)), e.g. by disabling cloud sync.</li>
|
||||
<li>Lodge a complaint with the
|
||||
<a href="https://ico.org.uk/" target="_blank" rel="noopener">Information Commissioner’s Office</a>
|
||||
if you think we’re mishandling your data.</li>
|
||||
</ul>
|
||||
<p>
|
||||
Email <a href="mailto:{{ OPERATOR_EMAIL }}">{{ OPERATOR_EMAIL }}</a> to
|
||||
exercise any of these.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section class="public-section">
|
||||
<h2 class="public-section__head">Children</h2>
|
||||
<p>
|
||||
The Service is not directed at, and is not intended for use by,
|
||||
anyone under 18. Do not create an account if you are under 18. If
|
||||
you believe a child has provided personal data to us, contact
|
||||
<a href="mailto:{{ OPERATOR_EMAIL }}">{{ OPERATOR_EMAIL }}</a> and we
|
||||
will delete it.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section class="public-section">
|
||||
<h2 class="public-section__head">Security incidents</h2>
|
||||
<p>
|
||||
If we discover a personal-data breach likely to result in a risk to
|
||||
your rights and freedoms, we will notify the ICO within 72 hours of
|
||||
becoming aware of it, as required by UK-GDPR Art. 33, and notify
|
||||
affected users without undue delay where Art. 34 requires.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section class="public-section">
|
||||
<h2 class="public-section__head">Changes to this notice</h2>
|
||||
<p>
|
||||
Material changes will be flagged in-app and dated above. Trivial
|
||||
edits (grammar, restructuring) won’t.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
{% endblock %}
|
||||
Loading…
Add table
Add a link
Reference in a new issue