Status: Accepted
Date: 2026-05-03 UTC
Deciders: software-architect
Scope: Hosting topology for support.raxx.app — which of three architectural options best meets the privacy, brand, and infra constraints
Design doc: docs/architecture/support-raxx-app.md
Refs: Epic #651 · ADR-0028 (status page hosting) · ADR-0002 (no stored credentials)
Three topology options exist for the customer-facing support portal:
Option A: New CF Pages project (raxx-support), static React SPA, calls Raptor API endpoints that proxy FreeScout. No direct customer → FreeScout traffic.
Option B: Public Antlers route inside the existing raxx.app app (e.g., raxx.app/support) that uses the same backend and auth.
Option C: FreeScout's own customer-facing portal UI, re-skinned via the Customization & Rebranding module.
The constraints to evaluate against:
1. No FreeScout/Tagras name ever visible to customers.
2. Privacy boundary enforced server-side (not in the browser).
3. No new Heroku app — Raptor endpoints on existing raxx-api-prod.
4. Confidence Engine visual identity (full design control).
5. Passkey-only authentication (platform invariant).
6. support.raxx.app subdomain (specified in epic #651).
7. CF Access gate absent — this is a public-access surface.
Option A: New CF Pages project raxx-support, calling Raptor API proxy endpoints.
This mirrors the proven status.raxx.app pattern (ADR-0028) and satisfies all six constraints above. The CF Pages project is a new deployment (raxx-support) distinct from raxx-status and raxx-app. Raptor endpoints are added to the existing raxx-api-prod dyno — no new Heroku app.
raxx-status playbook exactly. The S9 sub-card operator knows this pattern.support.raxx.app runs as a separate CF Pages project. A Antlers (raxx.app) deploy cannot affect the support portal and vice versa.support.raxx.app is a different origin from api.raxx.app. The design resolves this by using JWT Bearer tokens in sessionStorage rather than the session cookie — a well-understood pattern but slightly different from the main Antlers cookie flow.raxx-status pattern is already maintained.raxx.app/support) — the Raptor API contract is the same regardless of which origin the SPA is served from. Migration from separate origin to embedded route is a frontend-only change.raxx.app/supportRejected for this design run, not permanently.
Option B would share the Antlers codebase and avoid the cross-domain auth complexity. However, it conflicts with the support.raxx.app subdomain requirement in epic #651. It also couples the support portal release cycle to the main Antlers app — a support portal regression could require an Antlers hotfix. Option B remains a valid long-term consolidation path if the team wants to reduce surface count post-launch.
Rejected.
FreeScout's customer portal has a fixed URL structure under tickets.raxx.app (the same host as the operator UI, which is CF Access gated). Making it customer-accessible without CF Access would expose the operator UI host to the public network. Routing customers to a different subdomain (support.raxx.app) would require reverse-proxy rewriting of FreeScout's HTML — fragile and version-sensitive. The Customization & Rebranding module allows CSS and logo overrides but not full design control; the result would be FreeScout's layout with Raxx colors, not Confidence Engine. Passkey authentication cannot be integrated into FreeScout's auth flow without a custom FreeScout plugin (PHP, non-trivial). Option C fails constraints 1, 4, and 5 without significant custom FreeScout development that would need to be maintained across FreeScout upgrades.