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.
Caveats:
- The
ContractCreatedrow still lands inevents.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_flowfor 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):
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 (SignatureReceivedEvent → LegalDocumentsSignedEvent).
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.