# Ingestion and availability

PlayerPal is an asynchronous loyalty sidecar. Gameplay must not depend on it.

Long-form: spec §12, §34-§41. Decisions: ADR 0004, 0005, 0006, 0008, 0015.

## Failure rule

If every PlayerPal service is unavailable, the casino and provider must still authenticate the player, launch the game, accept bets, settle rounds and pay winnings.

```text
Player opens game → Operator launch → Provider game → Bet and settle
                                              ↓ (async, retry)
                                    Provider outbox / batch file
                                              ↓
                                    PlayerPal ingestion → loyalty / analytics
```

No per-player session creation, per-launch resolution, per-round token request or synchronous loyalty calculation.

## Call budget (minimum provider integration)

1. Receive an already-provisioned `pp_plr_prv_...` in the operator's launch metadata.
2. Write eligible activity to the provider's existing outbox in the same local transaction as settlement where possible.
3. Cache a machine token (refresh typically every 5-15 minutes per worker, not per player).
4. Send one compressed batch of hundreds or thousands of events.
5. Poll batch status only after an ambiguous response.

## Control plane vs data plane

Ingestion authenticates, validates a minimal envelope, appends durably, and responds `202`. Normalization, campaign evaluation, ledger updates and analytics run asynchronously off that durable log (ADR 0004). Real-time and batch paths share one event model and one reward engine.

`202` means durably accepted, not loyalty-complete. Ingest is integration-scoped (`x-integration-id`). Provider studio reads are a different plane: provider-Party scoped (`x-party-id`), numbers only ([ADR 0026](decisions/0026-analytics-subject-and-provider-cohort.md), [loyalty.md](loyalty.md)).

## Idempotency

At-least-once delivery, idempotent processing (ADR 0008). Uniqueness (spec §38.1):

```text
batch uniqueness:  (integration_id, batch_id)
event uniqueness:  (integration_id, event_id)
ledger uniqueness: (campaign_rule_version_id, activity_event_id, ledger_action)
```

An identical retry returns the original receipt. A reused ID with a different checksum returns `409 idempotency_conflict`.

Scope for events comes from the authenticated integration, not from the payload's operator/provider fields (ADR 0014).

## Batch delivery

Small/medium batches: `POST /v1/activity-batches`. Large exports: `POST /v1/activity-uploads` returns a short-lived signed PUT URL; the partner writes NDJSON, NDJSON.gz, JSON, or CSV to object storage, then `POST /v1/activity-uploads/{uploadId}/complete` with checksum, schema version, and client `batch_id` (spec §37.7, ADR 0006). Processing starts only after the stored object matches the checksum. The complete path then uses the same Activity → Points pipeline as inline batches.

Local MinIO is the S3-compatible store (`quay.io/minio/minio`, pinned by digest in compose). Preconditions: compose `minio` + `minio-init` (bucket `playerpal-activity`), `ACTIVITY_S3_*` in `infra/.env` (see `.env.example`). Host API uses `http://localhost:9000`; the compose API service uses `http://minio:9000` internally and signs PUTs for `http://localhost:9000`. Verify: `.cursor/skills/verify-playerpal/features/activity-uploads.md`. Parquet is recognised and rejected (`format_unsupported`) until a dedicated decoder is added.

## Event log phasing

Phase 1: PostgreSQL inbox (or object storage) and workers. Phase 2: Redpanda/Kafka-compatible log when measured throughput, extra consumers, or replay needs justify it (ADR 0005, spec §35.4). Do not guess a date.

## Suggested SLOs (not contractual until load-tested)

| Capability | Objective |
|---|---|
| Game launch/wager dependence on PlayerPal | None |
| Link and account APIs | 99.9% monthly |
| Batch acceptance API | 99.95% monthly |
| `202` latency for a normal batch | p95 under 500 ms after body received |
| Activity-to-ledger freshness | p95 under 5 minutes |
| Duplicate ledger effects | Zero by design |

Interactive/core and ingestion/processing scale independently (spec §41). Token validation uses cached JWKS. Ingestion does not synchronously call IAM, campaign admin or analytics.

Integration profiles (provider batch, operator-submitted activity, object-file, optional overlay): spec §40.
