Test coverage

This page renders the file the Playwright integration suite executes, e2e/scenarios.json, as it is. Every scenario runs as each of the profiles below, and the suite checks BOTH that the allowed profiles get through AND that every other one is refused - an access test that only proves the yes half proves nothing.

The profiles

Profile What this account is
root Root: full control of the gateway
infra-admin Gateway admin: routes and built-in pages only
app-admin Application admin: users, roles and identity settings only
tenant-admin Tenant admin: ADMIN member of the acme tenant
user Plain user: USER member of the acme tenant, no capability

Gateway scope

Scenario Probe Allowed
List the routes The routing plane belongs to the gateway scope: only root and infra-admin may read the route table; every other profile gets 403. GET /api/routes root infra-admin
Deposit an OpenAPI spec on a route A route may hold its contract as a deposited file rather than an upstream url, and the gateway then serves it on the route's own prefix. Depositing belongs to the routing plane: root and infra-admin only, every other profile gets 403. The probe names a route that does not exist, so an allowed caller gets 404 - which proves the barrier without depending on a fixture route. PUT /api/routes/no-such-route/spec?filename=orders.yaml root infra-admin
Choose which operations of a route are audited An audited operation's calls become audit events sent to the OpenTelemetry collector, with the caller, the status, the trace id and the fields the operation names. Choosing them belongs to the routing plane: root and infra-admin only, every other profile gets 403. The probe names a route that does not exist, so an allowed caller gets 404. PUT /api/routes/no-such-route/audit root infra-admin
Remove a route's deposited spec Removing drops the file AND the declaration that names it, in one move: a route left pointing at a file that is gone is the dangling reference this feature exists to avoid. Same plane and same holders as the deposit. DELETE /api/routes/no-such-route/spec root infra-admin
Preview a version predicate The editor asks the ENGINE what a version pattern extracts while it is being typed, rather than guessing in the browser: Go uses RE2, which refuses lookarounds JavaScript allows, so a preview computed there would promise a match the gateway turns down at save. It reads and writes nothing, but it belongs to the routing plane and follows the route table's scope. POST /api/routes/version-preview root infra-admin
Read the brick catalogue The catalogue of predicates and filters drives the route editor: gateway scope. GET /api/catalog root infra-admin
List the external authorities A directory or an identity provider is a third-party service reached by URL, with credentials: infra plane. An app admin configures who may do what, not what the gateway connects to. GET /api/auth-providers root infra-admin
Probe where a request lands Runs a request through the live matcher and reports every route's verdict, in order. Routing is the infra plane's: an app admin cannot inspect it. POST /api/routes/probe root infra-admin
Export the vault, encrypted Seals the entries the caller administers under a passphrase, and only those: an app admin's export holds no infrastructure entry, a tenant admin's holds only their organisation's. This is the half a configuration export never carries, which is what lets a configuration be public. A profile administering no vault at all is refused; 422 means the caller's scopes hold nothing yet. POST /api/vault/export root infra-admin app-admin tenant-admin
Export the whole configuration Downloads routes, roles, authorities, mail relay, themes and settings as one YAML file. Root only: a configuration crosses both planes at once, so administering one of them is not enough. Secrets never travel - a declared secret field leaves as its $name reference or not at all, which is what makes the file safe to put in a ticket or a git repository. GET /api/config/export root
What an export will not contain Lists the secret fields holding a literal rather than a vault reference: they stay behind, so the console can say so next to the download button, while the admin can still act on it. Lists as well every $name the configuration expects, which is what the target gateway will have to hold in its own vault. GET /api/config/report root
Preview what importing a file would do Reports what an imported file would add, change or remove, and which vault entries it expects that the vault does not hold, without writing anything. That preview is the one moment an admin still knows what a given secret was, which is why the missing ones are asked for right there. POST /api/config/preview root
List the saved configurations Several configurations coexist and one is active (CFG-02). Root only, like export and import and for the same reason: a configuration crosses both planes at once, so whoever handles one administers both. Reading stays open on the community image - what the Enterprise one adds is KEEPING several - so an installation coming down from it still sees what it saved. GET /api/configurations root
List the git locations The named git locations a configuration travels through (CFG-07): a repository, a branch and a directory each, so the same repository can hold one directory per customer platform. Root only, like every configuration - a location names a repository and a vault reference for its token, which is the installation's own business. GET /api/config-remotes root
Check a git location without writing to it Proves the repository answers, the credential is accepted and the branch exists, and writes nothing - so a wrong token is found on the screen that sets it rather than during the first push of a real change. A refused credential comes back with what that forge actually wants, because every forge reports a missing permission and a wrong basic-auth username as the same sentence. The probe names a location that does not exist, so a 404 proves the barrier without a fixture. POST /api/config-remotes/no-such/check root
Pull a configuration from git, applying nothing Reads a configuration out of its git location and SHELVES it: the document lands as a saved copy and the gateway goes on serving what it serves. The answer carries the plan - what activating it would add, change and remove - because the loop is pull, read, activate, read again. Whoever can write to the branch must not be able to reconfigure the gateway, which is why nothing here applies. POST /api/configurations/no-such/pull root
Push a saved configuration to git Commits the SAVED copy to its git location, attributed to the operator who clicked - which is what links the audit trail to the repository's history. A push replaces the location's directory, whatever is there, and never rewrites history: what it replaced stays in the repository's log. The one refusal left is somebody else pushing at the very same moment, and the cure is to push again. POST /api/configurations/no-such/push root
Send a mail relay test message The mail relay is infrastructure (a third-party host with credentials): only infra admins may fire the test (422 while it is not configured). POST /api/settings/mail-relay/test root infra-admin
Read where the database lives and what the gateway runs on The Database tab prints its backup, restore and migration procedures from this answer: the real paths, the platform the runtime describes (Kubernetes and its Helm release, a Swarm service, a compose project) and the copy a migration is in the middle of, never its password. Root only: it names the installation's insides. GET /api/backup/info root
Dismiss the remembered copy The gateway remembers the last copy into a PostgreSQL server, so the migration procedure survives a reload; an operator who went another way dismisses it. Root only, like the copy itself. DELETE /api/backup/last-copy root
List control-plane access tokens Control-plane tokens grant headless access to the admin port: this is what connects an agent (MCP-01). Anybody who administers a domain (root, infra-admin, app-admin) mints and lists their OWN: a token acts with its owner's capabilities, read again on every call, so it never hands out more than the person holds. The tokens authenticate on the admin plane only, never the data plane, and each carries a perimeter (readonly or full). GET /api/admin-tokens root infra-admin app-admin
The agent endpoint's switch The MCP endpoint ships OFF (MCP-01) and only root or an infra admin may open it: a control-plane token is required either way, but the door's existence is a decision somebody takes, not something inherited from an upgrade. GET /api/settings/agent root infra-admin
Change what a token may do A token is editable for everything that is NOT in the token. The secret is a hash in a column and encodes no perimeter, no domain, no address, no expiry and no name, so none of them has to be reissued to be changed - and whoever holds the key keeps holding the same key. Which is what makes NARROWING cheap, and that is the reason this exists: a read-only token in a service's deployment manifest should become a schedules one without minting a second and editing another team's repository, because that friction is why the safer move does not get made. Widening is possible too, and the audit records the before and after of every field. Open to whoever may mint one - root, infra-admin, app-admin - on their own tokens only: another person's answers not found. PUT /api/admin-tokens/no-such-token root infra-admin app-admin
Give a token a new secret A rotation, and what it KEEPS is why it is not simply minting a second one: the name, the perimeter, the domain, the addresses and the expiry all stay, so the audit keeps one history for one credential and the only thing to change anywhere is the value in one configuration file. The old secret stops working the moment this answers - which is what a rotation is, and why the console asks first. The new value travels exactly once, in this response, like a freshly minted one. Whoever may mint one, on their own tokens only. POST /api/admin-tokens/no-such-token/renew root infra-admin app-admin
The developer tunnel (plug) Whether the developer tunnel is open (DEV-11), the address developers type to reach it, what it is doing on this node, and who holds the developer capability with or without a deposited key - by fingerprint, never the key. Infrastructure, beside developer mode rather than under it: developer mode is a configuration of what the installation offers its developers, this opens a port into the cluster and uses the deployment's rights over it. The tunnel runs only when both say so, ships OFF, and the page where a developer deposits a key exists only while it is open. GET /api/settings/plug root infra-admin
Where this gateway's traces go The collector this gateway exports its spans to (OBS-04), and whether this image can export at all. Whoever edits routes - this is infrastructure, like TLS or the mail relay - and Enterprise to turn on: the trace context TRAVELS in both editions, and an identifier is put on every request whether or not anybody is tracing, because that identifier is what joins a line of the access log to a service's own business audit. What is sold is putting Meerkat on the trace: its inbound span, the span of the upstream call, and the gap between the two, which is the gateway's own time and the one number nobody else in the cluster can produce. What comes back is RAW: a header value of $otlp-token is the reference as written, never the secret behind it - the exporter resolves it at the moment of the call, so the credential is not in this answer, not in the audit trail and not on the screen. GET /api/settings/telemetry root infra-admin
Ask the collector whether it answers Posts an EMPTY OTLP batch to the address on screen, before anybody saves it: the whole path is exercised - name resolution, network, TLS, the credential, the collector's own answer - and no span is recorded anywhere. Gateway scope, like the setting it tests, because it makes this gateway call an address somebody hands it; the answer carries the status and the time it took, never the body. POST /api/settings/telemetry/test root infra-admin
Approving an agent is a root decision The consent page (MCP-07) hands an agent the approver's own powers over the whole gateway, so only root reaches it; everyone else is refused before seeing it. Root reaches the handler and 400s on the probe's unknown client, without registering anything. GET /oauth/authorize?response_type=code&client_id=none&code_challenge=x&code_challenge_method=S256 root
Drive the gateway from an agent One endpoint, the Model Context Protocol over Streamable HTTP. It sits behind the same door as the API, and the tools it offers are the caller's own: a signed-in session reaches it, the tools then answer with that person's powers. Off by default, hence the 404 on a fresh installation. POST /mcp root infra-admin app-admin tenant-admin user
The console documents the control plane Every signed-in console user may open the swagger-ui page: it lists Meerkat's own admin API, and nothing else - the routes' specs live on the data plane, for developers. Anonymous callers get 401. GET /apidocs/specs.json root infra-admin app-admin tenant-admin user
A service schedules a call to itself, and the gateway makes it Scheduled calls (SCHED-01): root mints a schedules-perimeter token, a backend creates a schedule on the control plane - saying which ROLES its call must carry, in the singular or the plural - finds it again by its own metadata, and the gateway calls the named route through its front door as "meerkat" holding them; the run closes with what answered. No account behind the call, and the token opens nothing else on that port. The history (SCHED-03) keeps every turn that ended, and a turn is asked for again by name - a new run carrying today's payload, which says in the history which one it replays. _flow_ user
List the TLS certificates What the gateway presents at a handshake is infrastructure, like the upstream of a route: only infra admins may read the certificates. Key material never appears in the payload, whoever asks. GET /api/certificates root infra-admin
Generate a self-signed certificate HTTPS in one click, for a laboratory or a first integration: the gateway is its own authority, and the certificate says so. POST /api/certificates/self-signed root infra-admin
Create a certificate signing request The offline door: the request leaves for whoever runs the authority, the private key stays. Nothing here needs a route to the internet. POST /api/certificates/signing-request root infra-admin
Ask the ACME authority for a certificate An order is added from a plane's Add certificate menu like the others and sits on that plane; the authority is asked on the first visit to a name once the order serves a door. Refused while no authority is set up, and for a wildcard or an address. POST /api/certificates/acme root infra-admin
List the ACME authorities The accounts certificates are asked of, and the providers the drawer offers. A literal account key never travels back. GET /api/acme/authorities root infra-admin
Set up an ACME authority Refused at save time rather than at renewal time: terms not accepted, a binding missing, a directory that does not answer - each named. POST /api/acme/authorities root infra-admin
Change an ACME authority Another directory is another account: it gets a fresh corner of the cache. On the community image the edition refuses first (422). PUT /api/acme/authorities/no-such root infra-admin
Delete an ACME authority Refused while certificates are still asked of it, and the refusal names them. DELETE /api/acme/authorities/no-such root infra-admin
Ask an authority again Once whatever the authority refused for is fixed - a DNS record, a closed port - the order is asked again. On the community image the edition refuses first (422). POST /api/certificates/no-such/retry root infra-admin
Place a certificate on a plane A certificate is made or imported once, then served on the console, the applications, or both. Another certificate already answering for one of its names on that door is named in the refusal, and replaced only when asked. PUT /api/certificates/no-such/placement root infra-admin
Read the TLS settings Which HTTPS doors are open, and whether an ACME authority issues on its own. The external-account HMAC key follows the vault rule: a literal never travels back. GET /api/settings/tls root infra-admin
Compare two saved configurations What changes going from one saved configuration to another, object by object, without looking at what runs (CFG-04): the question asked of a customer's configuration and the template it was made from, before touching either. Root only, like every configuration. GET /api/configurations/no-such/compare/other root

Application scope

Scenario Probe Allowed
Manage the built-in pages' themes The flow pages (login, profile) carry the PRODUCT's face - name, logo, colours - so their themes and branding are application scope. The gateway merely serves them. GET /api/themes root app-admin
Correct and add the built-in pages' languages The wordings of the sign-in page are the PRODUCT speaking, so they sit in application scope beside its theme and its brand. The same right covers correcting a shipped catalogue and adding a language the binary does not carry. GET /api/locales root app-admin
List the users The user base is the application's identity: app-admin scope. A gateway admin has no business reading it. GET /api/users root app-admin
Who may plug in Who holds the developer capability, with the keys they deposited by fingerprint, and whether the tunnel is open (DEV-13). The people of the tunnel are the application's: its administrator grants the capability, so it reads this; the tunnel's switch stays the infrastructure's. GET /api/plug/developers root app-admin
Who is plugged in now The plug runs open now, merged over the gateway's replicas (DEV-13), read by the administrator who can end one: the application's, who grants the developer capability. GET /api/plug/sessions root app-admin
Read the role catalogue The global hierarchical role catalogue (RBAC-01) is application scope. GET /api/roles root app-admin
List every account's application tokens The tokens each person created on their profile to call the applications' APIs (AUTH-09), with their owner: what an application administrator needs to find the one a departed colleague's script still carries, or the one that leaked. GET /api/data-tokens root app-admin
Revoke an account's application token Ends it at its next call, and writes token.revoke to the owner's line of the trail. An administrator ends a token here, never mints one: a credential is only created by its owner. DELETE /api/data-tokens/no-such-token root app-admin
List the live sessions Who is signed in where, right now (CONSOLE-08), with filters and pages. Root reads both planes; an application administrator the applications' sessions only, since who runs the console is root's business. GET /api/sessions root app-admin tenant-admin
End a session Signs one session out on every gateway (SEC-07), and writes session.revoke to the trail. A console session is root's alone: under an application administrator's eyes it reads as absent. DELETE /api/sessions/0000000000000000 root app-admin tenant-admin
Read the audit trail The audit trail is a transverse section scoped by capability (RBAC-05): root sees all, infra-admin the routing plane (routes, themes), app-admin the identity (users, roles, settings), a tenant admin only their tenants' events. Anyone who administers nothing is refused. GET /api/audit root infra-admin app-admin tenant-admin
Export the audit trail The trail as a CSV file (STORE-06): the filters of the screen and the perimeter of the caller, never more than the screen shows. Enterprise, and audited - the file carries addresses and names, and who took it out is a question somebody will ask. GET /api/audit/export?kind=security root infra-admin app-admin tenant-admin
How long the audit trail keeps an event The retention of the trail (AUD-02), from three months to five years. Root's alone: whoever may shorten the trail may erase their own traces with it. GET /api/settings/audit root
Read the issue reports The embedded issue tracker (ISSUE-03) is a transverse section: root and infra/app admins see every report, a tenant admin only those filed under their tenants. Anyone who administers nothing is refused. GET /api/issues root infra-admin app-admin tenant-admin
An app admin can never mint or promote a root Creating a root user or setting the root flag on an existing user requires root itself: the API answers 403 to an app-admin. _flow_ -

Infrastructure scope

Scenario Probe Allowed
Read the maintenance switch The switch that closes an installation to its visitors (LIFE-05) answers 503 for every route at once, without a single route being edited. Why it is closed is picked from a translated list rather than typed, because that page is read in twenty languages. Closing every application is an operator's act: only root and infra admins read or flip it, and whoever administers or develops can still go through to check the fix before reopening. GET /api/settings/maintenance root infra-admin
List what the runtime can route to What the runtime says exists beside this gateway (SVC-02), asked when a route is created: an upstream typed by hand is where the typos live, and none of them show up until traffic does. Always available in both editions and with no switch - what gates it is what the deployment grants, a mounted socket or a service account. An installation with no runtime to ask answers WHY rather than failing. GET /api/services root infra-admin
Subscribe to what a screen shows One websocket for the whole console, with a source per screen: the traffic curves, the scheduled calls, and the journal of administrative writes every screen watches to learn that what it is showing moved. It answers every ADMINISTRATOR - the routing plane's, the application's, an organisation's - and WHAT each may watch is decided at the upgrade, from the audit trail's own partition, then handed to the socket as a registry holding those sources and nothing else. A source cannot check it: the library shares one read between the subscribers of a topic. It is mounted BEHIND the same funnel as every API call, so a subscription passes the same checks: session, pending login, token perimeter, narrowing to the token's domain. A plain GET is not an upgrade, so an allowed caller is refused by the protocol rather than by the perimeter, which is the point of the probe. GET /api/live root infra-admin app-admin tenant-admin
Read what passed through Traffic per route over the last hour: requests by status class, latency, upstream failures (OBS-01). It tells a route that is failing from one nobody is calling - the two look identical in a configuration. Summed over every node of a cluster, and each interval says how many it covers, so a partial curve never passes for a total. In memory: a restart starts a new hour, and an installation wanting a year pushes the counters over OTLP (Enterprise) to a collector that writes them into the time-series database it already runs. With ?endpoints=1 it also answers WHICH endpoint of a route the time went to, over the period ?since asks for - the same one the caller is drawing, so a route's endpoints add up to the route rather than to a longer story nobody asked about. The unit is the template, never a raw path - one series per order is chosen by whoever sends the requests, which makes it a denial of service and not an imprecision. A name is either DECLARED (a deposited spec, the per-endpoint rules, or what the control plane resolved from the upstream) or DEDUCED from the shape of the paths seen, with identifier-looking segments folded to {id} and a per-route budget past which everything shares one bucket. Deduced lines are marked, because a shape can be read wrong. GET /api/metrics root infra-admin
Read which routes are answering The console has always shown what is CONFIGURED and never what WORKS (SVC-04). This reports what the circuit breaker already watches on every answer, per node - so it needs no prober of its own, and it sits on the routing plane's perimeter like the routes it describes. GET /api/routes/health root infra-admin
Read the gateway's own log The last lines this node wrote about itself (OBS-03), structured whatever the output format, for the console's Logs screen. Per node: in a cluster the answer names the node, and the whole cluster's log is the collector's business. The access log is not in it. GET /api/logs?limit=1 root infra-admin
Turn the log level up or down Live, on every node, and not stored: a restart comes back at the manifest's level. A level more talkative than the startup one goes back on its own after thirty minutes, because a gateway left at debug fills a disk. The probe sends a level that does not exist, refused after the guard, so the run never changes what the gateway writes. PUT /api/logs/level root infra-admin
Read the gateway's own limits How much memory the proxy may spend on a body it rewrites (PERF-02) is a property of the INSTALLATION, not of the application it serves - so it lives in the Routes page's Global drawer, with the other things that are true of every route at once, and an application administrator cannot move it. GET /api/settings/proxy root infra-admin
Read the issue tracker switch Turning user-filed issue reports on is an infrastructure decision (ISSUE-04): the switch sits on the Issues screen, ships OFF, and only infra admins read or flip it. GET /api/settings/issues root infra-admin

Console navigation

Scenario Probe Allowed
Read what the running release brought The version under the account button, and the notes of the minor or major release it belongs to: 1.0.0 for 1.0.1, never a patch's. Any signed-in account reads it. GET /api/release-notes root infra-admin app-admin tenant-admin user
The Infra rail entry shows only for its admins The console rail hides the Gateway scope from profiles that cannot use it (and the API refuses them anyway). _ui_ root infra-admin
The Application rail entry shows only for its admins Users, roles and identity settings are gated behind the app-admin capability in the console navigation. _ui_ root app-admin
The Tenants rail entry shows for tenant admins A tenant owner or ADMIN (or a tenant creator) sees the Tenants scope; plain members and capability admins without a tenant do not. _ui_ root tenant-admin
The Data plane rail entry shows for administrators What the applications are doing - their sign-ins, sessions, scheduled calls, traffic and issue reports - in one entry with a section each. It shows for anyone who may open one of those sections; each section is guarded on its own and the API scopes the content. _ui_ root infra-admin app-admin tenant-admin
The Meerkat rail entry shows for everybody holding the console The gateway itself - its audit trail without the applications' sign-ins, its own log, the console's sessions, the API reference, the release notes and the licence. Everybody reads the last two, so the entry shows for every profile; each other section shows only for who may open it. _ui_ root infra-admin app-admin tenant-admin user
Each profile lands on its own first screen Opening the console root: infra admins land on /infra/routes, app admins on /application/general, everyone else on their tenant. _flow_ root infra-admin app-admin tenant-admin user
Single-organisation mode is switched live, and gives the others back The shape of an installation is a setting read per request, not a value frozen at boot: switching it takes effect at once. Going down to one organisation holds the others back without deleting anything - the console is served knowing it, on the first paint - and switching up gives them and their people's access straight back. _flow_ root

Tenant scope

Scenario Probe Allowed
List a tenant's members Tenant administration is ownership or the ADMIN membership on THAT tenant: neither capability admins nor plain members reach another tenant's member list. GET /api/tenants/{tenantId}/members root tenant-admin
Read a member's sign-in history The sign-in history of a member is visible to root and to that tenant's owner or ADMIN, nobody else. GET /api/tenants/{tenantId}/members/{aliceId}/logins root tenant-admin
Transfer tenant ownership Ownership is a tenant field (TENANT-02): only root or the current owner may transfer it. A tenant ADMIN reaches the endpoint but is refused (403); root reaches the handler and 422s on the probe's unknown user, without changing anything. POST /api/tenants/{tenantId}/owner root
Exclusive group mode asks which group at sign-in With a tenant forced to SINGLE, a member holding several groups lands on /select-group after signing in (and again after each tenant switch); only the chosen group's roles apply to the session. _flow_ user

Application screens

Scenario Probe Allowed
Choose between one organisation and several The shape of the identity - who belongs where - so it is the app-admin's decision, not root's alone. The probe sends a mode that does not exist, refused after the guard, so the run never switches the installation it is testing. PUT /api/settings/tenancy root app-admin

Sign-in and profile flows

Scenario Probe Allowed
A wrong password is refused without a session The login page answers 401 with the same message whether the user or the password is wrong (no account enumeration) and no session cookie is issued. _flow_ -
A user sees their own sign-in history After signing in, /profile/security links to /profile/history where the fresh sign-in shows with its method. _flow_ user
The profile flow links back to the applications The profile hub (and the user button's Applications submenu) lists the UI routes the session may open - public, authenticated, and granted role-gated ones only. _flow_ user
The developer page refuses non-dev users /profile/dev answers 403 for a user without the dev capability. _flow_ -
The built-in captcha gates self-registration With the home-grown captcha on (the default), the register page carries a distorted-digits image; a wrong copy is refused, creates no account, and each challenge is single-use. _flow_ -
Self-registration end to end With the policy on and SMTP configured: the visitor registers, cannot sign in before confirming, opens the mailed one-shot link, the admins are notified, and the first sign-in lands on the waiting room until access is granted. _flow_ -
Personal API tokens authenticate API calls A user mints a token on Profile > Security > API tokens (shown once); it captures their current tenant+group context and authenticates 'Authorization: Bearer mk_...' calls to the data-plane API routes with no browser session. Disabling or revoking it - or disabling the account - stops it at the next request; the admin port never accepts it. _flow_ user
Brute-force sign-ins are throttled Failed sign-ins are counted per address and account within a configurable window (Security page): past the budget the endpoint answers 429 without even checking, and a success forgives the counter. Failures on one account from any address are counted too: past five the page asks for the picture code as well, an unknown name exactly like a real one, and never locks anything. Wrong two-factor codes are throttled per account the same way. _flow_ -
Forgot password resets through a mailed one-shot link The sign-in page links the flow (when SMTP is configured): a neutral outcome whatever the address, a 1-hour one-shot link that survives mail-scanner prefetches (GET peeks, POST consumes), and a successful reset revokes every open session and notifies the owner. _flow_ -
Disabling passkeys removes the affordance and refuses the ceremonies When the global passkey policy is off, the login page hides the passkey button and the WebAuthn endpoints answer 403. _flow_ -
An account awaiting access passes "signed in" and stops at "in an organisation" The belonging axis (RBAC-06) over a real gateway: a confirmed account that belongs to no organisation still passes a route gated on authentication - that level says who it is and nothing more - but a route asking for an organisation sends it to /account-pending rather than answering a bare 403. _flow_ -

Data plane

Scenario Probe Allowed
A mailed code signs in, and only in the browser that asked Sign-in by one-time code (AUTH-16), switched on for the run: the sign-in page offers it, the code arrives through the SMTP sink, it opens the session - and the same code pasted into a second browser is refused, which is what the request-id cookie is for. _flow_ user