Best practices

A checklist distilled from the package’s invariants. Each item links to the page that explains why.

Correctness under load

  • Never replace the atomic claim with a read‑then‑write. The conditional UPDATE in
    RedemptionService::claimSeat() is the only sanctioned path that bumps current_uses. Ship a
    two‑process concurrency test for any change to it.
    → Atomic idempotent redemption
  • Treat idempotency as a database invariant, not code discipline — rely on
    UNIQUE(code_id, redeemer_id) and UNIQUE(idempotency_key), and handle the violation (catch →
    release → idempotent success) rather than trying to avoid the race.

Tenancy

  • Scope every query through forTenant() / the BelongsToTenant trait. An unscoped
    where('code', …) mixes tenants and is a bug.
  • Bind a real TenantResolver in multi‑tenant hosts; single‑tenant apps need none.
    → Multi‑tenancy & host seams

Codes

  • Increase the length, don’t loop forever when you see collision_exhausted — the keyspace is too
    small for your issue volume.
  • Don’t add I L O U or duplicates to the alphabet — the generator refuses them because
    normalization would shrink the keyspace.
    → Invite codes

Secrets

  • Set INVITE_SIGNING_KEY and INVITE_PII_SALT to dedicated, rotatable secrets in production.
    The APP_KEY fallback is a dev convenience; rotating APP_KEY would otherwise orphan signed codes
    and PII hashes.
    → Configuration reference

Privacy

  • Leave store_network_fields off unless abuse review genuinely needs ip / fingerprint.
  • Schedule invite:prune-pii so PII is anonymized past the retention window — in place, so
    aggregates survive.
    → GDPR & data privacy

Authorization

  • Append your RBAC gate to routes.admin_middleware before exposing the admin API — the default is
    authenticate‑only, not authorize.
    → The HTTP API

Anti‑abuse

  • Tune thresholds and velocity rules; don’t disable the gate to “fix” false positives — use the
    allowlist instead.
  • Never surface the tripped signal_type to callers, and never make the gate throw on the
    redemption path (it must fail‑open).
    → Anti‑abuse scoring

Provisioning

  • Keep provisioners additive and idempotent (firstOrCreate, additive roles). The contract is
    GRANT‑never‑REVOKE, best‑effort.
    → Per‑invite entitlement grants

Observability

  • Listen to the domain events (CodeRedeemed, InvitationSent, InvitationAccepted) for perks,
    mail, and projections — CodeRedeemed fires once per genuine redemption.
    → Domain events
  • Read metrics from the API / MCP / MetricsService — they reconcile against the canonical rows.
    → Virality analytics

All of these invariants are encoded in the package’s CLAUDE.md, so an AI pair‑programmer inherits
them automatically. The package’s quality gate is composer check (Pint + PHPStan level max +
PHPUnit, on a PHP 8.3/8.4/8.5 × Laravel 12/13 matrix).