Skip to content

Pet

Pet insurance enrollment.

Dev-mode shortcut: ALAN_DEV_AUTO_ENROLL_PET=1

If you're not specifically working on the signature flow, set ALAN_DEV_AUTO_ENROLL_PET=1 on the backend process and click through the pet add-dependent UI as normal. The submission of the Confirmation step will:

  • skip the Dropbox Sign dispatch (uses SignatureProvider.manual),
  • tacitly approve the contracting package inline,
  • enroll the pet synchronously (bypasses the events_pipeline drain).

The LegalDocsSigning step auto-advances when it sees there's nothing to sign, so the dev clicks straight from Confirmation to Success.

ALAN_DEV_AUTO_ENROLL_PET=1 alan run --scope backend -- env APP=fr_api flask runserver

Caveats:

  • The ContractCreated row still lands in events.outbox_event. With no publisher running, it just sits there — harmless, but cleared by truncating the table or pointing the publisher at this DB later.
  • Dropbox Sign credentials are not required.
  • Devs touching the signature pipeline itself should use the full flow (or simulate_pet_signature_flow for headless testing) instead.

Headless end-to-end testing

simulate_pet_signature_flow reproduces the full add-pet + Dropbox Sign + enrollment chain without UI or Kay. It creates PetProfile, DependentDraft, InsurancePlan, contracting v2 package, synthesises the three Dropbox Sign callbacks, and ends with ContractCreated in events.outbox_event ready for the events_pipeline to deliver.

# 1. Drive the chain (mocks Dropbox Sign / PDF rendering / S3 upload).
alan run --scope backend -- env APP=fr_api flask pet simulate_pet_signature_flow \
  --owner-global-user-id "$(uuidgen)"
#   → prints pet_id / contract_id / approval_request_id

# 2. Verify enrollment state.
alan run --scope backend -- env APP=fr_api flask pet check_pet_enrollment \
  --pet-id <pet_id> --owner <owner_global_user_id>
#   → prints "RESULT: ENROLLED" once the chain has drained

The simulate command stops at the outbox row. Enrollment lands only if the events_pipeline publisher + consumer are running — see below.

Running the events_pipeline locally

The chain needs three pieces alive at once:

Process What it does Where it reads from
Flask backend (or simulate CLI) writes ContractCreated to PG outbox DB used by backend
events_publisher drains PG outbox → Redis stream DATABASE_URL, REDIS_URL
events_pipeline consumer reads Redis stream → runs handler REDIS_URL (DB shares the backend's)

Backend + local PostgreSQL

Backend writes streams to real Redis (without touching the RQ/broker chain, which stays on FakeRedis):

# In your backend / runserver shell:
export REDIS_STREAMS_URL=redis://localhost:6379

Don't set REDIS_URL on the backend — it would flip RQ to real Redis and require an RQ worker for the Dropbox webhook chain (SignatureReceivedEventLegalDocumentsSignedEvent).

Publisher (separate terminal):

cd events_pipeline/events_publisher
uv run python -m events_publisher start --log-level INFO
# defaults: DATABASE_URL=postgresql://localhost:5432/alan_backend, REDIS_URL=redis://localhost:6379/0

Consumer (separate terminal):

alan run --scope backend -- env APP=fr_api REDIS_URL=redis://localhost:6379 \
  flask events_pipeline consumer

Backend on Kay

The shared Kay database (--use-shared-kay, an anonymized production dump on Aurora refreshed daily, no setup) is the recommended way to get real data here. runserver, the publisher, and the consumer all support --use-shared-kay. All three must use it so they share one database — omit it on one process and that process lands on a different DB, after which the consumer reports rows that exist in psql as missing (SignatureReceived references missing ApprovalRequest, KeyError on signed documents).

# UI / runserver (terminal A)
alan run --scope backend -- env APP=fr_api flask runserver --use-shared-kay

# Publisher (terminal B) — must run from events_publisher/ dir for uv.
cd events_pipeline/events_publisher
REDIS_URL=redis://localhost:6379/0 \
  uv run python -m events_publisher start --use-shared-kay --kay-app fr_api --log-level INFO

# Consumer (terminal C)
alan run --scope backend -- env APP=fr_api ALAN_APP=fr_api \
  REDIS_URL=redis://localhost:6379 \
  flask events_pipeline consumer --use-shared-kay

Kay re-resolves a fresh Aurora IAM token whenever a process reconnects, so — unlike a manually-materialised DATABASE_URL with its static token — you don't have to restart by hand after ~15 min.

Diagnosing silence

If simulate_pet_signature_flow runs cleanly but check_pet_enrollment keeps reporting NOT_ENROLLED, the publisher or consumer isn't doing its job. Quick checks:

-- Are events queued?
SELECT stream_name, COUNT(*)
FROM events.outbox_event
WHERE published_at IS NULL
GROUP BY stream_name;

-- Who (if anyone) holds the publisher lease?
SELECT stream_name, owner_id, lease_until, lease_until < now() AS expired
FROM events.stream_lease
WHERE stream_name = 'stream:contracting:contract_changes';

Common causes: - Stale lease (expired=false, owner_id not your running publisher) → DELETE FROM events.stream_lease WHERE stream_name='stream:contracting:contract_changes' AND role='publisher'; publisher reacquires within --poll-interval (5s default). - Wrong DATABASE_URL → publisher sees an empty outbox. - Wrong REDIS_URL → publisher XADDs to a Redis the consumer can't see. - Split DB (UI/publisher/consumer not all on --use-shared-kay — e.g. consumer started without it) → consumer can't see committed approval requests/documents → SignatureReceived references missing ApprovalRequest / KeyError on signed docs. Use --use-shared-kay everywhere. - Stale Kay token → only with a manually-materialised DATABASE_URL (static token); Kay re-resolves the token on reconnect, so use --use-shared-kay everywhere to avoid it.