testing/cross_backend/token_scope_surface.ts

Cross-backend parity suite for token scoping's non-RPC surface rule — *a narrowed api token is RPC-only*.

Why this suite exists

Every existing cross-impl token-scope assertion is the rpc:<method> arm (the token_scope_cases batch in the conformance security slate). The surface:<name> arm had none — and it is the load-bearing half of the design. Both spines implement it, at the same named surfaces, returning a denial body that is an explicit cross-impl contract (token_scope_denied_bodyfuz_auth::token_scope_surface_denied_response), and nothing compared the two.

That gap has the shape this control family keeps finding: the per-method arm was the one everybody looked at, so it was the one that got a gate. The surface arm is where a divergence could sit indefinitely — the schema gate can't see it (no column), the action-manifest gate can't see it (no method), and the spec-derived suites can't see it (these are REST routes, not the declared RPC surface).

What it pins

  • The denial body, byte for byte — status, error, and the exact required_scope capability string, per surface. A spine that renamed a surface, dropped the surface: prefix, or emitted the JSON-RPC envelope from a REST route fails here.
  • The audit stream's channel denial — that surface is gated to credential_types: ['session'] on both spines, so a bearer never reaches its scope gate and the observable is credential_type_required instead. The case is kept here rather than moved: it is still the bearer-vs-surface probe, and what it now pins is the coarser-gate-first ordering both spines run (a spine that checked scope first would answer token_scope_required). surface:audit_stream's own capability string is consequently unreachable on the default mount and has no cross-impl pin — each spine's census holds its declaration, the way the WS upgrade's is held.
  • Scope outranks role — a narrowed token whose account *also* lacks the gating role must hear token_scope_required, not insufficient_permissions. This is the observable the TS↔Rust ordering divergence produced before both spines converged on running the surface gate ahead of the role gate; it was explicitly *invisible* to the cross-backend suites, because every scoped-token probe used a token whose account happened to hold the role.

Two non-vacuity controls hold the negatives honest: the narrowed token must still reach the RPC method it *does* name (otherwise a spine that rejected the credential outright would pass every denial), and a full bearer must reach fact_bare (otherwise a route broken for all bearers would pass too). The second has no audit-stream twin by construction — refusing every bearer is exactly what that route's channel gate is for.

Why the WS upgrade isn't here

Rule 3's third probeable surface is the WebSocket upgrade, and it is deliberately omitted: create_ws_transport threads cookies, not bearer headers, and a refused upgrade surfaces as a thrown connection error whose body — the thing this suite exists to compare — isn't readable. The HTTP surfaces are where a denial body is observable, which is what the parity gate needs. The WS gate keeps its per-spine coverage (fuz_app's surface census pins the call site; the Rust census pins its own).

$lib-free by contract (relative specifiers only), like the sibling cross-backend suites.

view source

Declarations
#

2 declarations

describe_token_scope_surface_cross_tests
#

TokenScopeSurfaceCrossTestOptions
#

Depends on
#