A seat-based product (meta.seat_based) sold at quantity N provisions N
assignable LicenseSeat rows instead of granting the buyer. Assigning a seat
runs the product's existing ProductAction grants against the seat holder via
an explicit `grantee` (resolution: grantee ?? purchaser ?? subscription->user),
so a seat confers exactly the product's access with zero duplicated grant logic.
- LicenseSeat model: assign / reassign / reclaim / retire / refreshGrant
- SeatService: idempotent provisioning for one-time purchases and subscriptions
(sizes the pool to quantity; renewal extends expiry + re-grants held seats)
- SeatAssigned / SeatReassigned / SeatRevoked events
- Product::isSeatBased(); ProductPurchase + Subscription provision on
completion / lifecycle hooks
- license_seats migration + factory; config `shop.seats` block
- 15 new tests (purchase, subscription, renewal, retire, delegation, gating)
Backward compatible: with no `grantee` the buyer grant is unchanged, and seat
behavior is gated behind the per-product meta flag + config `shop.seats.enabled`.
Full package suite green: 1425 tests / 3812 assertions / 0 failures.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>