testing/cross_backend/role_grant_offer_enumeration.ts

Cross-backend parity suite for the role_grant_offer_accept enumeration boundary — the deliberate 403-vs-404 split on the accept path.

role_grant_offer_accept distinguishes two denials, and the distinction is a conscious decision (not an accident to mask away):

  • a genuinely-nonexistent offer (or one on another account) → 404 role_grant_offer_not_found. The account-scoped IDOR guard refuses to confirm an offer id the caller's account doesn't own — so a cross-account prober learns nothing.
  • an offer that exists on the caller's OWN account but is targeted to a different actor (a sibling persona) → 403 role_grant_offer_actor_mismatch. This reveals "an offer exists for a sibling actor" to a co-account caller — but there is no cross-account leak: every actor in the distinction belongs to the one account already authenticated, so the 403 discloses nothing a principal can't already see about its own account. Masking this to 404 would only obscure a legitimate "not yours to accept, pick the right persona" signal. So 403 is the chosen, defensible behavior — and both spines must agree on it (a future Rust or TS change that over-masked the sibling case to 404, or under-masked the cross-account case to 403, is the regression this suite catches).

The actor-mismatch arm only fires for an *actor-targeted* offer accepted by a sibling actor, so the suite needs a multi-actor recipient. The keeper is the only fixture account that can be seeded multi-actor (extra_actors), and an account can't offer to itself — so the grantor is a separate admin account and the recipient is the keeper (actor A = fixture.actor is the offer target; actor B = fixture.extra_actors[0] is the rejected sibling).

Multi-step (create → accept) and using the acting selector, so this is an imperative suite (not a conformance_table row). The accept verb is on every spine's standard RPC surface, so the suite is ungated. Cross-process only: the sibling-actor / actor-targeted-offer setup is a wire flow. Cited property: docs/security.md §"Authorization" (the 404-over-403 mask is scoped to cross-principal leaks; an intra-account sibling-actor offer stays a 403 role_grant_offer_actor_mismatch).

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

view source

Declarations
#

3 declarations

describe_role_grant_offer_enumeration_cross_tests
#

OFFER_GRANTOR_USERNAME
#

testing/cross_backend/role_grant_offer_enumeration.ts view source

"offer_grantor" import {OFFER_GRANTOR_USERNAME} from '@fuzdev/fuz_app/testing/cross_backend/role_grant_offer_enumeration.js';

Username of the bootstrap-seeded single-actor admin grantor. The entrypoint seeds it via `extra_accounts: [{username: OFFER_GRANTOR_USERNAME, roles: [ROLE_ADMIN]}]`. A *separate* account is required (an account can't offer to itself), and it must be single-actor so its role_grant_offer_create resolves without an acting selector — which rules out the now-multi-actor keeper and fixture.create_account (whose internal invite_create runs as the multi-actor keeper and would hit actor_required).

RoleGrantOfferEnumerationCrossTestOptions
#

testing/cross_backend/role_grant_offer_enumeration.ts view source

RoleGrantOfferEnumerationCrossTestOptions import type {RoleGrantOfferEnumerationCrossTestOptions} from '@fuzdev/fuz_app/testing/cross_backend/role_grant_offer_enumeration.js';

Options for the offer-enumeration parity suite.

setup_test

Per-test fixture producer. Must be configured with extra_actors (≥1) so the keeper is multi-actor and the sibling-actor mismatch arm is reachable — the entrypoint passes default_cross_process_setup(handle, {extra_actors: [...]}).

type SetupTest

readonly

rpc_path?

RPC endpoint path. Default /api/rpc.

type string

readonly

Depends on
#