Raxx · internal docs

internal · gated

RBAC Antlers Surface Audit — Flag Inventory, Gap Report, and Denied-State Remediation

Status: Draft — awaiting operator review
Owner: software-architect
Date: 2026-05-14 UTC
Refs: #1449 (this card), #81 (SDLC epic), #970–#974 (RBAC phase cards), docs/architecture/rbac-design.md, docs/architecture/auth-unification-rbac-reconciliation.md


1. Context

Raxx today has no RBAC enforcement on Antlers (the customer-facing app). Every feature flag that controls a customer-facing Antlers surface is a global binary: ON for all authenticated users, or OFF for all authenticated users. As the platform moves toward a first external customer on 2026-05-23 UTC, three problems must be addressed before or shortly after launch:

  1. Global flags cannot express per-role access. The onboarding wizard, route guard, and passkey flows should be gated on authenticated customer role, not a global flag.
  2. The "Coming soon" pattern is live in Settings. Two flag-backed surfaces (options_backtest, live_mode_ring) display a visible "Coming soon" badge when the flag is OFF. This violates the locked invariant (2026-05-11 UTC): denied surfaces must be hidden, not grayed, badged, or "upgrade-to-unlock" framed.
  3. No permission name exists for any Antlers surface. The role taxonomy in rbac-design.md defines antlers-user, antlers-founders, antlers-pro tiers but no per-feature permission names. RBAC-aware flag resolution is Phase 2; this audit establishes the recommended permission names before Phase 2 implementation.

This document does not ship code. It is a pre-condition for dispatching implementation sub-cards.


2. Invariants (non-negotiable)

The following constraints govern every recommendation in this document:


3. Flag Inventory — Antlers Customer-Facing Surfaces

Scope: All flags where surface: antlers in feature_flags.yaml, plus flags on other surfaces (raptor, status-page) where the flag directly gates a customer-visible Antlers UI behavior. Console, Velvet, Queue, and CI flags are out of scope.

Total flags in file: 131 (as of 2026-05-14 UTC)
Flags with surface: antlers: 31
Flags on other surfaces with direct Antlers UI impact: 4 (noted below)
Flags audited: 35

3.1 Auth and Session Surfaces

Flag Surface / Route Current Gate Recommended Gate Denied-State Treatment Priority
route_guard RouteGuard in App.js — enforces session-aware redirect table across all authenticated routes Global ON/OFF Role: any authenticated session (antlers-user or above) — guard must fire on session presence, not flag presence. Role check is the auth session itself; no separate RBAC permission needed. N/A — guard is infrastructure, not a feature. Flag gates whether the guard runs at all. When OFF, routes are ungated. Must be ON for v1 launch. v1 blocker
passkey_signup_ui /signup — 5-step passkey enrollment + backup codes Global ON/OFF Role: unauthenticated visitor (public route). No role gate needed. Flag gates route registration. When OFF: route is not registered; /signup returns the catch-all redirect. Hidden by absence. v1 blocker
passkey_login_ui /login — passkey assertion flow Global ON/OFF Role: unauthenticated visitor (public route). No role gate needed. When OFF: route is not registered. Hidden by absence. v1 blocker
email_verification_ui /verify-email/pending + /verify-email/confirm Global ON/OFF Role: any authenticated or pre-auth session (public routes). No role gate needed. When OFF: routes not registered. Hidden by absence. v1 blocker
onboarding_wizard_v1 /onboarding/* — multi-step onboarding wizard Global ON/OFF Role: antlers-user (any authenticated new user). The wizard should not appear for users who have completed onboarding. Session state (onboarding_completed) is the gate, not a permission. Flag gates route registration; session state gates rendering. When OFF: route tree not registered; redirect to /setup or /dashboard. Hidden by absence. When ON but user already onboarded: redirect to /dashboard. v1 blocker
quebec_geoblock EmailFormStep + POST /api/marketing/validate-region — blocks QC signups Global ON/OFF (default OFF, must flip before launch) Role: N/A — geographic gate, not an RBAC gate. Remains global. When flag ON + QC jurisdiction detected: waitlist card renders in place of signup form. When flag OFF: no province check. v1 blocker
signup_geoblock_eu POST /api/auth/register/options — blocks EU/EEA+CH signups Global ON/OFF (default ON) Role: N/A — geographic gate, not an RBAC gate. Remains global. When ON + EU/EEA detected: block card renders. v1 blocker

3.2 App Shell and Navigation Surfaces

Flag Surface / Route Current Gate Recommended Gate Denied-State Treatment Priority
raxx_app_shell RaxxAppShell — full-bleed chrome wrapping all authenticated routes Global ON/OFF Role: any authenticated session (antlers-user or above). Shell is infrastructure; not per-feature. No separate permission. When OFF: legacy Bootstrap shell renders instead. No user-visible "denied" state — fallback is another shell. post-launch
antlers_visual_port_v1 /dashboardDashboardCE (Confidence Engine layout) Global ON/OFF Role: any authenticated session. Global flag; no per-role branching needed. When OFF: Dashboard.js renders instead. No denied state. post-launch
help_drawer_v1 ? icon in header tray → slide-over help drawer Global ON/OFF Role: any authenticated session (antlers-user or above). No restriction needed. When OFF: ? icon absent from header tray. Hidden by absence. post-launch
help_icon_v21 Replaces "Help" text button with moss-tinted ? tray icon Global ON/OFF Depends on help_drawer_v1. Same role gate. When OFF: v2.0 icon renders (or nothing if help_drawer_v1 is also OFF). post-launch
live_mode_ring LiveModeRing — breathing ring overlay when live trading is active Global ON/OFF Role: any authenticated session with live-trading access. When ring is OFF, it should not appear — but current Settings page shows it as "Coming soon" (gray badge) regardless of flag state. DENIED-STATE GAP — see §5. When OFF: ring overlay must not render AND the Settings → Sentinel ring nav item and badge must be hidden. v1 blocker (denied-state gap must be remediated before launch)

3.3 Trading Surfaces

Flag Surface / Route Current Gate Recommended Gate Denied-State Treatment Priority
trade_window_v1 /tradeTradeWindow page (symbol search, order entry, positions) Global ON/OFF Role: antlers-user minimum. Paper-first gate also applies — live order path is gated by paper-profitable-for-N-cycles invariant regardless of RBAC. When OFF: TradeWindow returns null (component already does this via isEnabled check). Route link in Header is conditionally rendered via isEnabled('trade_window_v1'). When OFF, link and page are both absent. Currently correct. post-launch

3.4 Backtesting Surfaces

Flag Surface / Route Current Gate Recommended Gate Denied-State Treatment Priority
options_backtest Settings → Platform Features tab — badge shows "Coming soon" when OFF Global ON/OFF Role: N/A — options_backtest is a backend licensing gate (data licensing in progress per #244). Current badge display is the denied-state violation. DENIED-STATE GAP — see §5. When OFF: the entire "Options Backtesting" row in Settings → Platform Features must be hidden, not badged "Coming soon." Route and endpoint remain gated via enable_options_backtest (backend). v1 blocker (denied-state gap)
advanced_risk_metrics Backtesting results — Sortino, Calmar, VaR, CVaR, Ulcer Index Global ON/OFF Role: antlers-user or antlers-pro (to be decided; recommend antlers-pro if this is a paid-tier differentiator, otherwise antlers-user). When OFF: metrics section absent from results. Hidden. Confirm no gray-out exists in current Backtesting.js. post-launch
walk_forward_validation Backtesting — walk-forward validation option Global ON/OFF Role: antlers-pro (advanced feature). When OFF: option absent from backtest form. Hidden. post-launch
custom_strategy_sandbox Backtesting — custom strategy editor Global ON/OFF Role: antlers-pro. When OFF: sandbox absent from UI. Hidden. post-launch
enable_options_backtest surface: raptor — HTTP 403 gate on options backtest endpoints Global ON/OFF N/A — backend endpoint gate, not UI surface. Antlers impact: companion options_backtest flag governs UI. When OFF: 403 from backend. UI should not expose the path in the first place when options_backtest is OFF. post-launch

3.5 AI Surfaces

Flag Surface / Route Current Gate Recommended Gate Denied-State Treatment Priority
ai_proposer AI strategy proposer surface (exact UI location TBD) Global ON/OFF Role: antlers-pro (or explicit permission antlers-feature-ai-proposer). Per project memory: AI augments understanding, never autonomous order execution. When OFF: surface absent. Hidden. post-launch

3.6 Onboarding and First-Run Surfaces

Flag Surface / Route Current Gate Recommended Gate Denied-State Treatment Priority
dashboard_first_run /dashboard — first-run welcome rail + empty-state widgets Global ON/OFF Role: antlers-user — any authenticated new user. Detection via positions.length === 0 && settings.first_dashboard_seen !== true. Role gate is session presence, not an RBAC permission. When OFF: legacy empty-state Dashboard renders. No denied state. post-launch

3.7 Demo and Marketing Surfaces (pre-auth / public)

Flag Surface / Route Current Gate Recommended Gate Denied-State Treatment Priority
demo_flow_ui /demo — 6-step visitor conversion flow (public, no auth) Global ON/OFF Role: N/A — public route. No auth required. No RBAC gate needed. When OFF: route not registered. Hidden by absence. post-launch
demo_founders_cta_variant DemoConversionOverlay — CTA variant text Global ON/OFF Role: N/A — variant logic, not an access gate. When OFF: standard CTA renders instead of Founders variant. No denied state. post-launch
demo_feasibility_readout /demo Step 2 — FeasibilityReadout component Global ON/OFF Role: N/A — public route feature. When OFF: readout absent from slider view. post-launch
demo_posthog_events Client-side PostHog capture on /demo Global ON/OFF Role: N/A — analytics toggle. When OFF: posthog.capture() is a no-op. post-launch
demo_clarity Microsoft Clarity session replay on /demo Global ON/OFF Role: N/A — analytics toggle. When OFF: Clarity not initialized. post-launch
getraxx_waitlist WaitlistSection.jsPOST /api/waitlist/subscribe Global ON/OFF Role: N/A — public marketing surface. When OFF: form is static no-op (existing behavior). post-launch
Flag Surface / Route Current Gate Recommended Gate Denied-State Treatment Priority
antlers_obfuscate_mode Portfolio, positions, backtest, trade-history — hides $ values, shows % Global ON/OFF Role: any authenticated session (antlers-user or above). Obfuscate mode is a user preference, not a tier gate. State is stored in localStorage. When ON globally, the toggle control is available to all authenticated users. No per-role restriction needed — operator decision: should all users have this? When OFF: toggle absent from Header/Settings. Dollar values always shown. Hidden toggle. post-launch

3.9 Observability and Auth Backend Flags with Antlers UI Impact

These flags live on surface: raptor or surface: antlers but directly govern what Antlers renders:

Flag Surface Antlers UI Impact Gate Denied-State Priority
render_id_emission antlers useRenderId() hook emits render events per page mount Global ON/OFF When OFF: hook is no-op. No visible UI change to user. post-launch
auth_email_verification raptor Backend email send/verify endpoints — companion to email_verification_ui Global ON/OFF When OFF: /verify-email/* routes still register if email_verification_ui is ON, but API calls 404. Handle gracefully. v1 dependency (must match email_verification_ui)
webauthn_registration raptor Backend passkey registration endpoints — required before passkey_signup_ui is meaningful Global ON/OFF When OFF: signup UI calls 404. Must be ON before passkey_signup_ui. v1 dependency
auth_webauthn_login raptor Backend passkey assertion endpoints — required before passkey_login_ui is meaningful Global ON/OFF When OFF: login UI calls 404. Must be ON before passkey_login_ui. v1 dependency

3.10 Flags Not Yet in App.js (UI not yet wired)

These flags are defined in feature_flags.yaml with surface: antlers but do not appear in current App.js routing or visible component files:

Flag Notes
broker_fidelity Epic 1 — Fidelity broker integration (UI not yet built)
options_chain Epic 1 — Options chain view (separate from options_backtest)
roi_goals_tracker Epic 1 — ROI goals tracker (UI not yet built)
sentiment_pipeline surface: raptor — backend-only, no Antlers UI yet
customer_docs_theme surface: antlers, area: docs — CSS asset for docs.raxx.app, not the raxx.app SPA
antlers_demo_mode Referenced in DemoContext.js but not present in feature_flags.yamlunresolved flag: see §7

Where a flag will eventually need per-role enforcement, the following permission names are proposed following the <app>-<resource>-<level> convention from rbac-design.md:

Surface / Feature Proposed Permission Name Minimum Role
Route guard (session presence) N/A — gate is session existence, not a permission antlers-user session
Onboarding wizard N/A — gate is onboarding_completed state antlers-user session
Trade window (paper mode) antlers-trade-paper antlers-user
Trade window (live mode) antlers-trade-live antlers-founders or above, plus paper-first gate
Advanced risk metrics antlers-backtest-advanced antlers-pro
Walk-forward validation antlers-backtest-advanced (same) antlers-pro
Custom strategy sandbox antlers-strategy-sandbox antlers-pro
AI proposer antlers-feature-ai-proposer antlers-pro
Obfuscate mode toggle N/A — user preference, not a permission antlers-user
Help drawer N/A — available to all authenticated antlers-user
Options backtesting antlers-backtest-options Blocked by licensing (#244), not RBAC; defer permission until licensing resolves

Phase 2 note: These permission names are for planning purposes. Phase 2 (per-role flag resolution) must confirm B1 enforcement compatibility before any permission-conditional flag resolution ships.


5. Denied-State Audit — Current Gray/Disable Violations

This section inventories surfaces that currently show a grayed, badged, or "Coming soon" affordance when the controlling flag is OFF. Each is a violation of the locked invariant (2026-05-11 UTC) and requires a remediation card before v1 launch.

Gap 1: Settings → Platform Features — "Coming soon" badges (Settings.js, lines 617–638)

Location: frontend/trademaster_ui/src/pages/Settings.js, Settings → Platform Features tab
Current behavior:
- When options_backtest is OFF: "Options Backtesting" row renders with a gray secondary badge reading "Coming soon" and a tooltip explaining the licensing status. - When live_mode_ring is OFF: "Live Mode Ring" row renders with a gray secondary badge reading "Coming soon."

Violation: Both rows are visible to all authenticated users regardless of flag state. The platform is communicating "this exists but you can't have it yet" — exactly the framing the invariant prohibits.

Required remediation: - When options_backtest is OFF: hide the entire "Options Backtesting" row entirely. No badge, no tooltip, no row. - When live_mode_ring is OFF: hide the entire "Live Mode Ring" row, AND hide the "Sentinel ring" nav item in the Settings left nav (line 348, already conditionally rendered — confirm it is gated correctly). - The introductory copy ("Features shown as 'Coming soon' are in active development...") must also be removed or revised once the rows are hidden, since it describes the pattern being eliminated.

Remediation card: See sub-card recommendation in §6 (Gap-1 card).

Gap 2: console_drawer_actions flag — "Re-check disabled + tooltip for roles below ops"

Location: backend_v2/api/feature_flags.yaml line 555, flag console_drawer_actions
Surface: surface: console — this is a console flag, NOT an Antlers customer-facing flag.
Assessment: Out of scope for this audit. The console drawer actions gray pattern is an operator-facing surface. The hide-don't-gray rule applies to customer-facing Antlers surfaces; console surfaces for operators have different UX conventions. No remediation required under this audit.

Gap 3: antlers_demo_mode — referenced in code but absent from feature_flags.yaml

Location: frontend/trademaster_ui/src/context/DemoContext.js line 28
Current behavior: DemoContext.js calls isEnabled('antlers_demo_mode'). This flag name does not exist in feature_flags.yaml. When window.__FLAGS__ does not define it, the flag silently defaults to false — demo mode is globally disabled.
Risk: Undefined flag behavior. If demo mode is intended to be active, it may silently not work. If it is intentionally not in the YAML (because it is set some other way), that should be documented.
Assessment: Not a denied-state violation, but a flag inventory gap. See §7 (open questions).


6. v1 RBAC Gaps — Must-Fix Before 2026-05-23 UTC Launch

These are the items that must be resolved before first external user:

# Gap Flag(s) Required Action Blocker?
G-1 "Coming soon" badges in Settings options_backtest, live_mode_ring Hide both rows when flags are OFF. Remove associated introductory copy. Yes — denied-state invariant violation
G-2 Route guard must be ON route_guard Flag must be flipped ON before launch. Requires passkey_login_ui also ON. No code change needed if guard logic is complete; this is an ops flip. Yes — no auth gate without it
G-3 Passkey auth backend + UI flags must be ON passkey_signup_ui, passkey_login_ui, webauthn_registration, auth_webauthn_login All four must be ON for a working auth flow. Backend flags gate the API; UI flags gate the routes. Sequencing: backend first, then UI. Yes — no user auth without these
G-4 Email verification flags must be consistent email_verification_ui, auth_email_verification Both must be ON together, or both OFF together. Mismatched state (UI ON, backend OFF) will produce 404 errors on the verification API. Yes — UX breaks without parity
G-5 Onboarding wizard gating onboarding_wizard_v1 Wizard routes must require an authenticated session (via route_guard). Current implementation relies on FLAG_ROUTE_GUARD for session gating — confirm that wizard routes are nested inside the guarded route tree, not publicly accessible. Yes — security: unauthenticated users must not reach onboarding routes
G-6 Quebec geoblock must be ON quebec_geoblock Must flip to ON before launch. CAD $30,000/day OQLF exposure (Bill 96). Hard deadline 2026-05-23 UTC. Yes — legal

v1 RBAC gap count: 6 (G-1 through G-6)

Note: G-2 through G-5 are operational flips, not code changes, assuming the underlying feature work is complete. G-1 requires a code change in Settings.js. G-6 is an operational flip.


7. Open Questions (require operator decision before sub-cards are claimed)

  1. antlers_demo_mode flag registration. DemoContext.js references isEnabled('antlers_demo_mode') but the flag is absent from feature_flags.yaml. Is this flag intentionally managed outside the YAML (e.g., derived from demo_flow_ui), or is it a missing entry? Operator must decide: add a YAML entry or remove the isEnabled call and derive demo-mode state from demo_flow_ui directly.

  2. options_backtest row: hide entirely or hide until licensing resolves? The audit recommends hiding the row when the flag is OFF. However, if Kristerpher wants a waitlist-capture affordance for users interested in options backtesting (similar to the Quebec waitlist pattern), the treatment changes. Current recommendation: hide entirely; no waitlist row. Confirm.

  3. antlers_obfuscate_mode scope. Currently global ON/OFF. Should it be available to all authenticated users (any tier), or restricted to a specific tier? Recommend: available to all antlers-user-and-above. If restricted to antlers-pro, a denied-state gap would be created (the toggle must be hidden for non-pro users). Operator must lock this before Phase 2 RBAC implementation.

  4. advanced_risk_metrics, walk_forward_validation, custom_strategy_sandbox tier assignment. Are these antlers-pro-only or available to all authenticated users at v1? If pro-only, the denied-state treatment for non-pro users is: these options are simply absent from the backtest form. Operator must confirm tier assignment before permission names are finalized.

  5. ai_proposer tier. Same question as above. Given the product thesis (AI augments understanding, never autonomous execution), this surface may be appropriate for all authenticated users or only pro. Confirm.

  6. B1 compatibility of role-conditional flag resolution. Phase 2 (per-role flag resolution) must confirm that adding role conditions to flag evaluation does not break B1 enforcement (every flag in feature_flags.yaml must have a console_flag_promotions migration row before prod flip). This is a design constraint on Phase 2, not Phase 1.


8. Rollout Compatibility

All flags audited here are currently global ON/OFF. No schema migrations are required to implement the denied-state remediations (G-1 is a React render change only). The RBAC phase is Phase 2 and is not a prerequisite for v1 launch, except where explicitly marked as a blocker above.

The flag reconciler (ADR-0085) is not affected by this audit. Flag values themselves are not changed by this document.


9. Security Considerations



Awaiting operator review. No code changes will be dispatched from this audit until Kristerpher approves.