testing/cross_backend/role_grant_offer_lifecycle.ts

Cross-backend parity suite for the role-grant-offer decline and retract refusals — the error paths beneath the happy-path WS notification suite (describe_role_grant_offer_notification_ws_tests), which is the only other cross-process caller of these two verbs.

What both spines must agree on, per the handlers (auth/role_grant_offer_actions.ts and the queries behind them):

  • not yours → 404 role_grant_offer_not_found. Decline is scoped to the recipient account and retract to the grantor actor, so an offer the caller is not party to answers the same not_found as an id that names no offer (docs/security.md §"404-over-403 is the general mask"), and the offer is left pending. The scope check comes before the terminal check, so a stranger learns nothing of an offer's state either.
  • a decline or retract answers the terminal offer — the offer as the history then lists it, declined_at (with any decline_reason) or retracted_at stamped, as accept answers its own.
  • already decided → 400 invalid_request with reason role_grant_offer_terminal — a decline or retract after an accept, a second decline, a second retract, and either one after the other.
  • a grantor's lost authority ends its pending offers. Revoking a grantor's grant, or deleting its account, supersedes every pending offer it made — gone from the recipient's inbox, an accept answers role_grant_offer_terminal, and a role_grant_offer_supersede row records it (role_grant_revoked with the grant as cause, account_deleted with the account). The keeper's own admin grant refuses a revoke, 403 cannot_revoke_keeper_admin.
  • one audit row per success, none per refusal. A decline writes role_grant_offer_decline attributed to the recipient and targeting the grantor, a retract role_grant_offer_retract attributed to the grantor and targeting the recipient, each with its event's metadata; the refusals above write nothing. Read through list_audit_events, which drains first and parses each row's metadata against its event's schema.

Multi-step (each case needs an offer in a given state), so this is an imperative suite; the single-request rows — decline and retract of an id that names no offer — live in the conformance security slate. The verbs are on every spine's standard RPC surface, so the suite is ungated. The keeper is the grantor, so the fixture's keeper must hold ROLE_ADMIN (extra_keeper_roles), and it offers an admin-grantable role — the role option, SPINE_PARTICIPANT_ROLE by default; ROLE_ADMIN serves a backend with only the builtin roles.

view source

Declarations
#

2 declarations

describe_role_grant_offer_lifecycle_cross_tests
#

RoleGrantOfferLifecycleCrossTestOptions
#

testing/cross_backend/role_grant_offer_lifecycle.ts view source

RoleGrantOfferLifecycleCrossTestOptions import type {RoleGrantOfferLifecycleCrossTestOptions} from '@fuzdev/fuz_app/testing/cross_backend/role_grant_offer_lifecycle.js';

inheritance

role?

The role offered — one the backend's registry holds with the admin grant path. Default SPINE_PARTICIPANT_ROLE, the spine's own; a backend with only the builtin roles passes ROLE_ADMIN.

type string

readonly

Depends on
#

Imported by
#