compliance: flag-gate AI portfolio + cloud sync + Stripe; de-risk prompts; harden reviewer

Implements docs/read-markets-compliance-changes.md as flag-gated changes
(no deletions) so paused features stay in the tree for future re-enable.
All four flags default False so a fresh deploy is compliance-safe.

- New env flags: PORTFOLIO_AI_ENABLED, PORTFOLIO_SYNC_ENABLED,
  TICKER_UNIVERSE_AGGREGATE_ENABLED, SUBSCRIPTIONS_ENABLED.
- Gates: /api/analyze, /api/portfolio/sync*, /api/stripe/*, /pricing,
  ticker_universe writes, portfolio_analysis.analyse(). is_paid_active()
  returns True for any auth'd user when subscriptions are paused.
- Prompts (PROMPT_VERSION 10): universal _COMPLIANCE_RIDER prepended to
  every system prompt; watch list removed; price-target / close-above-below
  / trigger / forward-state-as-description rules added; SPECULATIVE
  pivoted to regime-only scenarios; daily + weekly digests tightened.
- Reviewer: deterministic regex/lexicon pre-check fail-closed under the
  Haiku call; portfolio rider gated by PORTFOLIO_AI_ENABLED; base prompt
  sharpened for forward-state and MAR forward-opinion patterns;
  ReviewerVerdict audit table; generate_with_review retry helper.
- Migration 0026: purge portfolio_sync + ticker_universe; create
  reviewer_verdicts.
- Copy: MAR cite fixed to Art 3(1)(35) + Art 20 + Del Reg 2016/958;
  portfolio reframed as browser-only viewer in disclaimer / privacy /
  terms / about / pricing / landing (en + it). TODO(legal) marker for
  lawyer sign-off on disclaimer.
- Tests: 13 lexicon + 6 reviewer compliance regressions; conftest enables
  all flags so existing 402 tests still cover their code paths.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Giorgio Gilestro 2026-05-29 19:57:12 +02:00
parent ee8384f1ba
commit 47dce1a1a4
38 changed files with 1188 additions and 279 deletions

View file

@ -18,6 +18,7 @@
<div class="settings-row__value">{{ user.email }}</div>
</div>
{% if SUBSCRIPTIONS_ENABLED %}
<div class="settings-row">
<div class="settings-row__label">Tier</div>
<div class="settings-row__value" style="display:flex; align-items:flex-start; gap:10px; flex:1;">
@ -59,8 +60,9 @@
{% endif %}
</div>
</div>
{% endif %}
{% if paid and paid.active and paid.source != "credit" and user.stripe_customer_id %}
{% if SUBSCRIPTIONS_ENABLED and paid and paid.active and paid.source != "credit" and user.stripe_customer_id %}
<script>
(function () {
var btn = document.getElementById('stripe-portal-btn');
@ -104,7 +106,9 @@
<span class="neu">Investing &rarr; Your Pie &rarr; &middot;&middot;&middot; &rarr; Export</span>.</span>
</p>
<div id="drop-zone" class="dz" data-paid="{{ 'true' if paid and paid.active else 'false' }}">
<div id="drop-zone" class="dz"
data-paid="{{ 'true' if paid and paid.active else 'false' }}"
data-sync-enabled="{{ 'true' if PORTFOLIO_SYNC_ENABLED else 'false' }}">
<input type="file" id="file-input" name="file" accept=".csv,text/csv" hidden>
<div class="dz__icon"></div>
<div class="dz__label">Drop your broker's portfolio CSV here</div>
@ -264,6 +268,9 @@
</details>
{# --- Cloud sync block --------------------------------------------- #}
{# Gated by PORTFOLIO_SYNC_ENABLED — see app/config.py. Holdings stay
in the browser when the flag is off; this whole section is hidden. #}
{% if PORTFOLIO_SYNC_ENABLED %}
<details class="settings-section">
<summary class="settings-section__head">Cloud sync (encrypted)</summary>
<p class="settings-section__lede">
@ -287,6 +294,7 @@
</p>
{% endif %}
</details>
{% endif %}
{# Future: Paddle subscription block, AI-spend ledger summary, etc. #}
@ -295,7 +303,7 @@
</div>
</section>
{% if user and paid and paid.active %}
{% if PORTFOLIO_SYNC_ENABLED and user and paid and paid.active %}
<div id="sync-modal" class="modal"
style="position:fixed;inset:0;background:rgba(0,0,0,0.45);
display:none;align-items:center;justify-content:center;z-index:1000;">