Prevents the last owner from leaving, requires two-step ownership transfer, revokes sessions and tokens on leave, and writes an audit record for every membership change.
Package status: reference context ready for human review. The contract and test scenarios are complete, but no claim is made that an adopting implementation has passed them.
Decision
An organization always retains at least one owner. The last owner cannot leave or demote themselves. Ownership transfer is two-step: the current owner nominates a living member, and that member must accept; only then does the nominee become owner and the nominator take the profile’s post-transfer role. Leave or removal immediately revokes that user’s sessions, refresh tokens, and org-scoped API tokens. Every join, leave, role change, and transfer writes an audit event with actor, target, and before/after role.
Scope
- Join from accepted invite, voluntary leave, removal, and ownership transfer.
- Last-owner invariant and two-step transfer.
- Session and token revocation on loss of membership.
- Audit events for membership mutations.
Outside this block
- Invite token hashing and expiry.
- Row-level query internals, except lost membership must fail later authorization.
- Seat billing except as a consumer of member count.
Contract
- Leave or self-demote that would drop owner count to zero is rejected with
last_owner_required. - Transfer requires
nomination_idplus acceptor identity matching the nominee; a single owner call that nominates and completes is rejected. - After leave or removal, requests using that user’s org session or org API token return
unauthorizedand those hashes are revoked. - Join uses the role from the invite or request record, not a client-supplied role field.
- Audit is written in the same transaction as the mutation or via a transactional outbox; a mutation without an audit row fails the contract.
- Authorization fails closed immediately even if a UI cache still lists the org.
Implementation
Lock owner counts when applying leave or role changes. Store nominations with expiry. On leave, revoke org-scoped sessions and tokens with reason membership_ended. Give audit events stable ids.
Failure handling
If the nominee left before accept, expire the nomination and keep the original owner. If token revocation lags, retry remainder; do not restore membership. If audit insert fails, abort the mutation. Concurrent last-owner leaves must still yield last_owner_required for one caller.
Verification
Cover last-owner leave, two-step transfer, skip-step transfer, leave-then-API-call, and audit presence. Execute the packaged scenarios against membership APIs.
The executable-looking examples in this package are fixtures and acceptance contracts. Run
python tools/validate.py from the collection root to check package structure and metadata; then
implement and execute the scenarios in the target repository.
Adoption assumptions
- Names and numeric values in
example.yamlare an adoption profile, not universal defaults. - The adopting team must map actors, data classes, error vocabulary, and ownership to its system.
References
- Last-owner and two-step transfer are project decisions for this product, not a statutory membership model.