Antlers flag RBAC audit — 2026-05-15 UTC
Auditor: qa-agent
Dispatched by: Kristerpher Henderson
Date: 2026-05-15 UTC
Refs: #1449, docs/architecture/rbac-antlers-surface-audit.md (PR #2082)
Invariant under test: Every customer-facing flag surface in the Antlers React app must hide denied features, never gray them out or badge them "Coming soon."
Memory anchor: feedback_hide_dont_gray_unavailable_features, locked 2026-05-11 UTC.
Method
Static code audit of frontend/trademaster_ui/src/ — the live Playwright environment was not available in this run (no dev server running in this worktree). All findings are grounded in the component source and confirmed against the issue thread. The prior architecture doc (docs/architecture/rbac-antlers-surface-audit.md) was used as a reference baseline; this audit independently re-derives conclusions from source.
Grep patterns used: useFlag, isEnabled, Coming soon, disabled, opacity, pointer-events, bg.*secondary, badge.*secondary, subscription_required.
Scope: surface: antlers flags in feature_flags.yaml plus flags in App.js controlling routing. Analytics toggles (demo_posthog_events, demo_clarity, landing_posthog_events, render_id_emission) and console flags were excluded — they have no customer-visible denied state.
Total flag call sites reviewed: 28 across 14 source files.
Surfaces reviewed
| # | Surface | Flag | File | Denied behavior | Verdict |
|---|---|---|---|---|---|
| 1 | Settings → Platform Features tab — Options Backtesting row | options_backtest |
Settings.js L617–624 |
Flag OFF: row renders with gray secondary Badge reading "Coming soon" and a tooltip. Row is always visible. |
FAIL |
| 2 | Settings → Platform Features tab — Live Mode Ring row | live_mode_ring |
Settings.js L627–640 |
Flag OFF: row renders with gray secondary Badge reading "Coming soon." Row is always visible. |
FAIL |
| 3 | Settings → Sentinel ring nav item | live_mode_ring |
Settings.js L348–355 |
Flag OFF: nav item is hidden via {isEnabled('live_mode_ring') && …}. Correct. |
PASS |
| 4 | LiveModeRing overlay component |
live_mode_ring |
LiveModeRing.js L171–177 |
Flag OFF: visible = false; ring does not render. Hidden. |
PASS |
| 5 | /trade route — Trade Window link in header |
trade_window_v1 |
Header.js L553–561 |
Flag OFF: Nav.Link not rendered. Hidden by absence. |
PASS |
| 6 | TradeWindow page component |
trade_window_v1 |
TradeWindow.js L171 |
Flag OFF: component returns null. Hidden. |
PASS |
| 7 | RouteGuard — session-aware redirect table |
route_guard |
RouteGuard.js L107–112 |
Flag OFF: guard is bypassed; children render ungated. Not a denied-state display issue — a v1 blocker on ops side. | PASS (display) |
| 8 | /signup route |
passkey_signup_ui |
App.js L181–183 |
Flag OFF: route not registered. Hidden by absence. | PASS |
| 9 | /login passkey flow |
passkey_login_ui |
App.js L347 (shell branch) |
Flag OFF: login route not registered. Hidden by absence. | PASS |
| 10 | /verify-email/* routes |
email_verification_ui |
App.js L184–189 |
Flag OFF: routes not registered. Hidden by absence. | PASS |
| 11 | /onboarding/* wizard routes |
onboarding_wizard_v1 |
App.js L202–213 |
Flag OFF: route tree not registered. Hidden by absence. | PASS |
| 12 | /demo visitor flow route |
demo_flow_ui |
App.js L194–196 |
Flag OFF: route not registered. Hidden by absence. | PASS |
| 13 | Dashboard — DashboardCE vs Dashboard |
antlers_visual_port_v1 |
App.js L222 |
Flag OFF: Dashboard.js renders instead. No denied state — fallback is another working page. |
PASS |
| 14 | Full-bleed app shell | raxx_app_shell |
App.js L332 |
Flag OFF: legacy Bootstrap shell renders. No denied state. | PASS |
| 15 | Dashboard — first-run rail | dashboard_first_run |
DashboardCE.js L233–262 |
Flag OFF: first-run rail absent; standard dashboard renders. Hidden. | PASS |
| 16 | Header tray — obfuscate toggle | antlers_obfuscate_mode |
Header.js L56 |
Flag OFF: toggle absent from header. Hidden. | PASS |
| 17 | Header tray — help drawer icon | help_drawer_v1 |
Header.js L57, RaxxAppShell.js L47 |
Flag OFF: ? icon absent. Hidden. |
PASS |
| 18 | Header tray — v2.1 help icon | help_icon_v21 |
Header.js L58, RaxxAppShell.js L48 |
Flag OFF: new icon absent; v2.0 icon or nothing renders. Hidden. | PASS |
| 19 | Demo overlay CTA variant | demo_founders_cta_variant |
DemoConversionOverlay.js L106 |
Flag OFF: standard CTA renders. No denied state. | PASS |
| 20 | Demo mode kill-switch | antlers_demo_mode |
DemoContext.js L28 |
Flag OFF: demo mode disabled globally. No UI denied state. | PASS (but see Finding F-3 below — flag missing from YAML) |
| 21 | Quebec signup block | quebec_geoblock |
SignupPage → EmailFormStep |
Flag OFF: province check skipped; no block card shown. No denied state — ops flip before launch. | PASS (display) |
| 22 | EU signup block | signup_geoblock_eu |
PasskeyEnrollStep path |
Flag ON (default): EU block card renders when EU jurisdiction detected. No gray-out — full replacement card. | PASS |
| 23 | OptionsBacktestComingSoon component |
ENABLE_OPTIONS_BACKTEST |
OptionsBacktestComingSoon.js |
Component exists, renders a "coming soon" card — but has no non-test caller in current source. Dead component in current build. | PASS (not wired) — see Finding F-4 |
| 24 | DataFeed subscription_required badge |
N/A (feed metadata) | DataFeedSelector.js L136, DataFeedStatus.js L290, Settings.js L394 |
Warning badge on subscription-required feeds. This is informational metadata on a user-controlled feed config, not a feature-flag denied state. Users can still select the feed. Not a hide-don't-gray violation. | PASS |
| 25 | Backtesting feed disabled option |
N/A (user config) | Backtesting.js L283 |
<option disabled> for user-disabled data feeds. User-controlled config, not a feature-flag gate. Not a hide-don't-gray violation. |
PASS |
Findings
F-1 (FAIL) — Settings → "Coming soon" badge: options_backtest
File: frontend/trademaster_ui/src/pages/Settings.js, lines 617–624
Behavior: When options_backtest is OFF, the "Options Backtesting" row in Settings → Platform Features renders with a gray Badge bg="secondary" reading "Coming soon" and an OverlayTrigger tooltip explaining the licensing status. The row is unconditionally visible to all authenticated users.
Violation: The invariant requires hidden, not badged. The tooltip copy ("Data licensing in progress — options historical data will unlock this feature") compounds the violation by framing the feature as locked/unavailable-for-upgrade.
Required fix: Wrap the entire feature-flag-row div in {isEnabled('options_backtest') && (…)} so the row is absent when the flag is OFF. Remove or relocate the introductory copy at line ~600 ("Features shown as 'Coming soon' are in active development…") — once the rows are hidden, this copy describes a pattern that no longer exists.
Follow-up issue: see below.
F-2 (FAIL) — Settings → "Coming soon" badge: live_mode_ring
File: frontend/trademaster_ui/src/pages/Settings.js, lines 627–640
Behavior: When live_mode_ring is OFF, the "Live Mode Ring" row renders with a gray Badge bg="secondary" reading "Coming soon." Row is unconditionally visible.
Violation: Same pattern as F-1.
Note: The Sentinel ring nav item in the Settings left nav (line 348) is already correctly gated with {isEnabled('live_mode_ring') && …}. This nav item passes. Only the Platform Features tab row is failing.
Required fix: Wrap the feature-flag-row div at line 627 in {isEnabled('live_mode_ring') && (…)}.
Follow-up issue: same issue as F-1 (both fixes belong in one card).
F-3 (FLAG GAP) — antlers_demo_mode missing from feature_flags.yaml
File: frontend/trademaster_ui/src/context/DemoContext.js, line 28
Behavior: DemoContext.js calls isEnabled('antlers_demo_mode') as a kill-switch. This flag name is absent from backend_v2/api/feature_flags.yaml. When window.__FLAGS__ does not define it, the flag silently resolves to false — demo mode is globally disabled regardless of hostname or URL param.
Classification: Not a denied-state display violation. A flag inventory gap. Demo mode being silently off could mask a bug if demo_flow_ui is flipped ON for testing.
Required action: Operator must decide — (a) add antlers_demo_mode to feature_flags.yaml with a B1 migration row, or (b) remove the isEnabled('antlers_demo_mode') call and derive kill-switch from demo_flow_ui directly. The decision must happen before the demo surface is live.
Follow-up issue: separate card from F-1/F-2.
F-4 (DEAD CODE) — OptionsBacktestComingSoon component is unwired
File: frontend/trademaster_ui/src/components/OptionsBacktestComingSoon.js
Behavior: Component renders a full-page "Options backtesting — coming soon" card. Its utils/featureFlags.js docstring says callers should render this component when isOptionsBacktestEnabled is false. But no non-test file imports or renders this component in the current codebase. The component would be a violation of the invariant if wired in.
Classification: Not currently a live violation (the component is dead). But it is a trap — a future developer could wire it in and reintroduce the "coming soon" pattern.
Required action: Either delete the component (preferred, since its purpose conflicts with the invariant), or add a code comment explicitly prohibiting its use per the 2026-05-11 hide-don't-gray decision and open a card to remove it cleanly.
Follow-up issue: low-urgency cleanup, but should not survive to v1 launch given it is a "coming soon" component by design.
Follow-up issues filed
Two issues filed from this audit:
-
[F-1 + F-2] Settings "Coming soon" badges must be hidden — area:frontend, type:bug, v1 blocker
Hide bothoptions_backtestandlive_mode_ringrows in Settings → Platform Features tab when their flags are OFF. Remove the introductory "features shown as Coming soon" copy. ~5 lines of JSX per row. -
[F-3]
antlers_demo_modeflag missing fromfeature_flags.yaml— area:frontend, type:bug
Operator decision required: add YAML entry + B1 migration row, or remove theisEnabledcall and derive fromdemo_flow_ui. -
[F-4] Delete
OptionsBacktestComingSooncomponent — area:frontend, type:tech-debt
Component conflicts with the hide-don't-gray invariant by design. No non-test callers. Remove before v1 to prevent accidental re-introduction.
Recommendation
The Antlers codebase is mostly compliant with the hide-don't-gray invariant: 23 of 25 surfaces reviewed either hide by absence (route not registered, component returns null) or render a full replacement view (no partial graying). Two surfaces in Settings — the "Options Backtesting" and "Live Mode Ring" rows in the Platform Features tab — are active violations and must be remediated before the 2026-05-23 UTC launch. The fix is mechanical (wrap each row in an isEnabled conditional), not architectural. The OptionsBacktestComingSoon dead component and the antlers_demo_mode YAML gap are lower urgency but should not reach v1 unresolved: the dead component is a trap for a future "coming soon" regression, and the YAML gap will silently disable demo mode if demo_flow_ui is ever turned ON for testing without the companion YAML entry. Pre-launch verdict: needs action on F-1 and F-2 before 2026-05-23 UTC.