Files
zeekayandHanzo AI b0c6e55220 e2e: the staking route is gone, so stop declaring it
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]>
2026-08-28 12:38:25 -07:00

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