The stLUX vault page was removed from the console — no vault is configured for any brand, so the surface only ever said so, and staking returns are served by /dashboard/calculator and /dashboard/validators from the P-Chain. lux.cloud is a catch-all origin, so the old path still paints the console's "No such section"; a bodyLen assertion against that shell passes and proves nothing. It moves to NOT_SERVED, where the registry keeps retired paths visible. Co-authored-by: Hanzo Dev <[email protected]>
376 lines
14 KiB
TypeScript
376 lines
14 KiB
TypeScript
import { env } from './env'
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// THE ROUTE REGISTRY — every page of every product, declared once.
|
|
// ---------------------------------------------------------------------------
|
|
// The suite used to test surfaces by hand, one `page.goto` per spec, which is
|
|
// why 62 Safe pages, 25 MPC pages and the entire admin surface had zero tests
|
|
// between them while `api.lux.financial` — whose API is dead — carried 293.
|
|
// Coverage was a function of which spec somebody happened to write, not of what
|
|
// the products actually serve.
|
|
//
|
|
// So the routes become DATA. One list per surface, iterated by 60-surfaces
|
|
// (anonymous) and 61-console (signed in). Adding a page to a product is one
|
|
// line here and it is covered at all three viewports on all four environments.
|
|
// No spec hard-codes a path again.
|
|
//
|
|
// ── Every route below was MEASURED on 2026-07-26, not copied from source ────
|
|
// The deployed apps and their repos have drifted: explore.lux.network is a
|
|
// Blockscout build with several upstream pages switched off (/pools,
|
|
// /name-services, /epochs, /batches, /blobs, /csv-export and /login all 404),
|
|
// so declaring them would be asserting a fiction. Routes that 404 today are
|
|
// listed under NOT_SERVED at the bottom — kept visible, deliberately not
|
|
// asserted, so nobody "rediscovers" them and re-adds a red test.
|
|
//
|
|
// ── HTTP STATUS IS NOT EVIDENCE ─────────────────────────────────────────────
|
|
// SEVEN of these nine origins answer 200 for a path that does not exist —
|
|
// measured with a `/zzz-bogus` control against each, and the count went from
|
|
// three to seven the moment the control was actually run instead of assumed:
|
|
//
|
|
// lux.cloud /dashboard/zzz-bogus → 200 catch-all
|
|
// exchange.lux.network /zzz-bogus → 200 catch-all
|
|
// bridge.lux.network /zzz-bogus → 200 catch-all
|
|
// lux.id /zzz-bogus → 200 catch-all
|
|
// wallet.lux.network /zzz-bogus → 200 catch-all
|
|
// mpc.lux.network /zzz-bogus → 200 catch-all
|
|
// superadmin.lux.cloud /zzz-bogus → 200 catch-all
|
|
// safe.lux.network /zzz-not-a-real-route → 404 honest
|
|
// explore.lux.network /zzz-not-a-real-route → 404 honest
|
|
//
|
|
// `catchAll` records that, and 60-surfaces treats those surfaces as RENDER-ONLY
|
|
// — the assertion is that the page painted something, never that it returned
|
|
// 200. This is the same trap that made a deleted KMS route look alive, and the
|
|
// two honest origins get a live control test each run so the distinction stays
|
|
// true rather than remembered.
|
|
|
|
export type Auth = 'anon' | 'login-redirect' | 'session'
|
|
|
|
export interface Surface {
|
|
/** Stable id — names the screenshot file and the test title. */
|
|
id: string
|
|
/** Human label for the run log. */
|
|
label: string
|
|
/** Origin, always resolved from env.urls — never a literal here. */
|
|
base: string
|
|
/**
|
|
* anon — renders the real page with no session.
|
|
* login-redirect — bounces to an IdP/login screen; that bounce IS the pass.
|
|
* session — needs a signed-in browser (61-console).
|
|
*/
|
|
auth: Auth
|
|
/** Origin answers 200 for unknown paths, so only rendering proves anything. */
|
|
catchAll?: boolean
|
|
routes: string[]
|
|
}
|
|
|
|
const u = env.urls
|
|
|
|
// ── Cloud console — lux.cloud ──────────────────────────────────────────────
|
|
// 44 routes under /dashboard, every one of which had ZERO authenticated
|
|
// coverage before 61-console. Anonymously they serve the SPA shell (200 even
|
|
// for /dashboard/zzz-bogus), so the anon sweep only proves they route; the
|
|
// signed-in sweep is what proves they work.
|
|
export const CLOUD_PUBLIC = ['/', '/login', '/pricing', '/docs'] as const
|
|
|
|
export const CLOUD_DASHBOARD = [
|
|
'/dashboard',
|
|
'/dashboard/analytics',
|
|
'/dashboard/api-keys',
|
|
'/dashboard/assets',
|
|
'/dashboard/assets/nfts',
|
|
'/dashboard/assets/tokens',
|
|
'/dashboard/billing',
|
|
'/dashboard/bridge',
|
|
'/dashboard/calculator',
|
|
'/dashboard/chains',
|
|
'/dashboard/deploy',
|
|
'/dashboard/dex',
|
|
'/dashboard/explorer',
|
|
'/dashboard/gas',
|
|
'/dashboard/iam',
|
|
'/dashboard/infrastructure',
|
|
'/dashboard/infrastructure/clusters',
|
|
'/dashboard/infrastructure/lux',
|
|
'/dashboard/infrastructure/lux/map',
|
|
'/dashboard/infrastructure/monitoring',
|
|
'/dashboard/infrastructure/networks',
|
|
'/dashboard/infrastructure/nodes',
|
|
'/dashboard/infrastructure/observability',
|
|
'/dashboard/infrastructure/services',
|
|
'/dashboard/infrastructure/storage',
|
|
'/dashboard/infrastructure/volumes',
|
|
'/dashboard/markets',
|
|
'/dashboard/monitoring',
|
|
'/dashboard/mpc',
|
|
'/dashboard/organization',
|
|
'/dashboard/organization/billing',
|
|
'/dashboard/organization/settings',
|
|
'/dashboard/organization/team',
|
|
'/dashboard/rpc',
|
|
'/dashboard/safes',
|
|
'/dashboard/settings',
|
|
'/dashboard/storage',
|
|
'/dashboard/treasury',
|
|
'/dashboard/usage',
|
|
'/dashboard/validators',
|
|
'/dashboard/wallets',
|
|
'/dashboard/webhooks',
|
|
] as const
|
|
|
|
// The screens the OWNER named: "manage bridge fees; wallets; mpc; treasury
|
|
// deploy and see all contracts safe, nodes explorer exchange bridge". These get
|
|
// asserted harder than the rest — they are the admin dash's reason to exist,
|
|
// and a placeholder on one of them is a product gap, not a cosmetic one.
|
|
export const CLOUD_ECONOMY: string[] = [
|
|
'/dashboard/treasury',
|
|
'/dashboard/safes',
|
|
'/dashboard/mpc',
|
|
'/dashboard/bridge',
|
|
'/dashboard/wallets',
|
|
'/dashboard/validators',
|
|
'/dashboard/dex',
|
|
'/dashboard/chains',
|
|
'/dashboard/deploy',
|
|
]
|
|
|
|
export const SURFACES: Surface[] = [
|
|
{
|
|
id: 'cloud',
|
|
label: 'Cloud console (lux.cloud)',
|
|
base: u.cloud,
|
|
auth: 'anon',
|
|
catchAll: true, // /dashboard/zzz-bogus → 200
|
|
routes: [...CLOUD_PUBLIC],
|
|
},
|
|
|
|
// ── IdP — lux.id ─────────────────────────────────────────────────────────
|
|
{
|
|
id: 'id',
|
|
label: 'Identity (lux.id)',
|
|
base: u.id,
|
|
auth: 'anon',
|
|
catchAll: true, // /zzz-bogus → 200
|
|
routes: ['/', '/login', '/signup', '/forget', '/select/login', '/account'],
|
|
},
|
|
|
|
// ── Explorer — per-network, moves with LUX_ENV ────────────────────────────
|
|
// The only surface besides RPC and the bridge that is genuinely deployed per
|
|
// network: explore / explore-test / explore-dev all resolve and serve.
|
|
{
|
|
id: 'explore',
|
|
label: `Explorer (${new URL(u.exploreLux).host})`,
|
|
base: u.exploreLux,
|
|
auth: 'anon',
|
|
routes: [
|
|
'/',
|
|
'/blocks',
|
|
'/txs',
|
|
'/accounts',
|
|
'/tokens',
|
|
'/stats',
|
|
'/gas-tracker',
|
|
'/advanced-filter',
|
|
'/verified-contracts',
|
|
'/api-docs',
|
|
'/dex',
|
|
'/chains',
|
|
'/validators',
|
|
],
|
|
},
|
|
|
|
// ── Exchange — 49 shipped routes, 0 of them running until now ─────────────
|
|
// Addressed through exchangeOrigin: the vanity host fails TLS from this
|
|
// network (see env.ts). helpers.exchangeBase() re-checks at runtime.
|
|
{
|
|
id: 'exchange',
|
|
label: 'Exchange',
|
|
base: u.exchangeOrigin,
|
|
auth: 'anon',
|
|
catchAll: true, // /zzz-bogus → 200
|
|
routes: [
|
|
'/',
|
|
'/swap',
|
|
'/limit',
|
|
'/send',
|
|
'/buy',
|
|
'/sell',
|
|
'/trade',
|
|
'/markets',
|
|
'/explore',
|
|
'/explore/tokens',
|
|
'/explore/pools',
|
|
'/positions',
|
|
'/positions/create',
|
|
'/pool',
|
|
'/pool/v2',
|
|
'/add',
|
|
'/remove',
|
|
'/portfolio',
|
|
'/bridge',
|
|
'/teleport',
|
|
'/options',
|
|
'/vote',
|
|
],
|
|
},
|
|
|
|
// ── Bridge ────────────────────────────────────────────────────────────────
|
|
{
|
|
id: 'bridge',
|
|
label: 'Bridge',
|
|
base: u.bridge,
|
|
auth: 'anon',
|
|
catchAll: true, // /zzz-bogus → 200
|
|
routes: ['/', '/transfer', '/history', '/status'],
|
|
},
|
|
|
|
// ── Wallet ────────────────────────────────────────────────────────────────
|
|
{
|
|
id: 'wallet',
|
|
label: 'Wallet',
|
|
base: u.wallet,
|
|
auth: 'anon',
|
|
catchAll: true, // /zzz-bogus → 200
|
|
routes: ['/', '/portfolio', '/send', '/receive', '/stake', '/swap', '/settings'],
|
|
},
|
|
|
|
// ── Safe — 31 served routes, previously ZERO tests ────────────────────────
|
|
// This is the treasury surface. It held no coverage at all while the Lux
|
|
// Foundation treasury (0xE54cAf7E) lives behind it.
|
|
//
|
|
// Every route paints the same 709-character wallet gate ("Connect your wallet
|
|
// to create a Safe Account or watch an existing one") until a wallet is
|
|
// connected — correct for a Safe, and the reason these tests assert that the
|
|
// route PAINTS rather than that it shows a balance. Reaching the funded
|
|
// screens needs a connected signer, which this suite deliberately does not
|
|
// carry: it must never be able to sign.
|
|
{
|
|
id: 'safe',
|
|
label: 'Safe',
|
|
base: u.safe,
|
|
auth: 'anon',
|
|
routes: [
|
|
'/',
|
|
'/welcome',
|
|
'/home',
|
|
'/balances',
|
|
'/balances/nfts',
|
|
'/balances/positions',
|
|
'/transactions',
|
|
'/transactions/queue',
|
|
'/transactions/history',
|
|
'/transactions/messages',
|
|
'/transactions/tx',
|
|
'/apps',
|
|
'/apps/open',
|
|
'/apps/custom',
|
|
'/apps/bookmarked',
|
|
'/settings/setup',
|
|
'/settings/appearance',
|
|
'/settings/modules',
|
|
'/settings/notifications',
|
|
'/settings/security',
|
|
'/settings/data',
|
|
'/settings/environment-variables',
|
|
'/settings/safe-apps',
|
|
'/new-safe/create',
|
|
'/new-safe/load',
|
|
'/spaces',
|
|
'/swap',
|
|
'/stake',
|
|
'/bridge',
|
|
'/addOwner',
|
|
'/address-book',
|
|
],
|
|
},
|
|
|
|
// ── MPC — public entry ────────────────────────────────────────────────────
|
|
{
|
|
id: 'mpc',
|
|
label: 'MPC (public)',
|
|
base: u.mpc,
|
|
auth: 'anon',
|
|
catchAll: true, // /zzz-bogus → 200
|
|
routes: ['/', '/login'],
|
|
},
|
|
|
|
// ── MPC — the protected surface, including the bridge-fee screens ─────────
|
|
// MEASURED: all 13 return 307 to the login screen with no session. That
|
|
// redirect is the assertion — an MPC console that served /bridge/fees or
|
|
// /wallets to an anonymous browser would be the finding.
|
|
{
|
|
id: 'mpc-protected',
|
|
label: 'MPC (protected)',
|
|
base: u.mpc,
|
|
auth: 'login-redirect',
|
|
catchAll: true,
|
|
routes: [
|
|
'/vaults',
|
|
'/wallets',
|
|
'/transactions',
|
|
'/settlements',
|
|
'/intents',
|
|
'/policies',
|
|
'/whitelist',
|
|
'/audit',
|
|
'/team',
|
|
'/bridge/fees',
|
|
'/bridge/contracts',
|
|
'/bridge/wallets',
|
|
'/settings',
|
|
],
|
|
},
|
|
|
|
// ── Staking / build ───────────────────────────────────────────────────────
|
|
{
|
|
id: 'build',
|
|
label: 'Build (lux.build)',
|
|
base: u.staking,
|
|
auth: 'anon',
|
|
routes: ['/', '/deploy', '/quickstart', '/sdk', '/login'],
|
|
},
|
|
|
|
// ── Admin — the operator surface, previously unnamed in any spec ──────────
|
|
// "admin.lux.cloud" appeared in exactly zero specs before this registry.
|
|
//
|
|
// MEASURED: every one of these six paths serves the SAME 62-character screen
|
|
// to an anonymous browser — "Lux Admin / SuperAdmin / Sign in with Lux IAM to
|
|
// continue. / Sign in". The gate is client-side, so the URL never changes and
|
|
// the status is always 200; classifying it 'anon' would have asserted that
|
|
// /users renders a user list when it renders a login button. It is a
|
|
// login-redirect surface whose redirect happens to be in-place.
|
|
{
|
|
id: 'superadmin',
|
|
label: 'Superadmin (superadmin.lux.cloud)',
|
|
base: u.superadmin,
|
|
auth: 'login-redirect',
|
|
catchAll: true,
|
|
routes: ['/', '/login', '/dashboard', '/users', '/orgs', '/settings'],
|
|
},
|
|
]
|
|
|
|
// ── Declared upstream, NOT served by these deployments (measured 404) ───────
|
|
// Kept as a record so the gap stays visible without being asserted. If one of
|
|
// these starts serving, move it into the list above — that is a product change,
|
|
// and it should be a deliberate one.
|
|
export const NOT_SERVED: Record<string, string[]> = {
|
|
explore: ['/pools', '/name-services', '/epochs', '/batches', '/blobs', '/csv-export', '/login'],
|
|
build: ['/builds'], // 307 — gated, not broken
|
|
// The staking route was removed with the stLUX vault page: no vault is
|
|
// configured for any brand, so the surface only ever said so. Staking returns
|
|
// now live on /dashboard/calculator and /dashboard/validators, both declared
|
|
// above and both reading the P-Chain. lux.cloud is a catch-all origin, so this
|
|
// path still paints the console's "No such section" — asserting it would pass
|
|
// on the 404 shell and prove nothing.
|
|
cloud: ['/dashboard/staking'],
|
|
}
|
|
|
|
export function surface(id: string): Surface {
|
|
const s = SURFACES.find((x) => x.id === id)
|
|
if (!s) throw new Error(`no surface "${id}" in the route registry (e2e/routes.ts)`)
|
|
return s
|
|
}
|
|
|
|
/** Total declared pages — printed by the sweep so coverage is a number. */
|
|
export const ROUTE_COUNT = SURFACES.reduce((n, s) => n + s.routes.length, 0)
|
|
+ CLOUD_DASHBOARD.length
|