git:20260912.eef1cca to git:20260913.51c1b6d

23 added, 26 removed. Audit A to A.

---
name: entitlement-grant
- description: >
- Grant, restore, and revoke what was paid for. Use when a purchase
- succeeds and the item is missing, when debug VIP leaks into story-slot,
- or when restore is a dead button. Stacks on game-monetization.
+ description: Use when verified purchases, subscriptions, durable unlocks, consumables, or time-limited access must be granted exactly once across retries, crashes, duplicate callbacks, and multiple devices.
---
# Entitlement grant
- Ask the column.
+ This skill owns verified purchase-event → durable entitlement state. Store UI and pricing belong elsewhere; `restore-purchase` owns user-initiated/current-entitlement reconstruction.
- | Column | Lands how |
+ ## Product classes
+
+ | Class | Durable state |
|---|---|
- | durable-unlock | once, restore-safe (remove-ads, act, cosmetic) |
- | consumable-stack | count += N, no restore |
- | time-pass | start → expiry, server or signed clock |
- | battle-track | season id + tier |
+ | non-consumable/durable-unlock | boolean/ownership keyed by product plus store identity |
+ | consumable | balance mutation keyed by a unique transaction/purchase token/event id |
+ | subscription/time-pass | entitlement interval/status keyed by the store's stable subscription identity/token |
+ | battle-track/season | ownership scoped to season/content identity |
- ## Order
+ ## Idempotent transaction state machine
- ```
- store says ok → verify receipt → write entitlement → refresh HUD / inventory
- restore tap → same verify → same write (idempotent)
- ```
+ Use the immutable store transaction ID, purchase token, or equivalent verified event ID as the deduplication key; do not use UI callbacks or order display strings as the primary key.
- Client-only "I bought it" flags are not durable-unlock.
- Debug VIP writes lab-slot only (save-integrity). It is not a receipt.
+ A safe abstract flow is:
- ## Iron rules
+ 1. receive `pending`/purchased event and persist processing state;
+ 2. verify authenticity/account/product and confirm it is grantable;
+ 3. in one durable transaction, record the event ID and apply the entitlement/balance mutation if that event ID is not already committed;
+ 4. only after durable grant, acknowledge / consume / finish according to product/store requirements;
+ 5. if acknowledgement/consume/finish fails, retry that store-side completion **without granting again**;
+ 6. emit UI refresh from durable entitlement state.
- - Restore exists on the wall and in settings for every durable-unlock SKU.
- - A second grant of the same durable id is a no-op, not a duplicate item.
- - Consumable-stack does not use restore. Say so on the SKU card.
- - Expiry uses a trusted clock. Local date rollback does not extend a pass.
- - Failed verify: keep the pending UI, do not grant, do not eat the receipt.
- - Combat columns do not read entitlements to shorten hitstop or cancels.
+ For stores that require consumption before re-purchase, preserve enough pending state that a crash cannot lose a consumed-but-not-accounted grant. Platform adapters may vary the exact store call sequence, but they must prove no crash window can duplicate or lose value.
- ## Accept
+ `PENDING` or unverified purchases do not grant. Revocation/refund handling updates entitlement through an explicit policy rather than deleting unrelated story progress.
- Kill the app after a sandbox purchase; the unlock is still there. Restore on a second device grants once. A debug flag does not survive a story-slot load.
+ ## Acceptance
+
+ Replay the same transaction/purchase token many times; kill after verification, after durable grant, and before/after acknowledge/consume/finish. The resulting durable entitlement or consumable delta is applied exactly once, and store completion can retry independently. Test pending→purchased and refund/revoke paths. Unrun sandbox/server cases stay `not-run`.