Nobody has started this yet — be first.
Business impact
POST /notifications currently accepts a data payload of any size, and every notification gets written into Redis twice -- once into the backlog entry, once into the message fanned out to every live subscriber -- and stored again for every entry retained under the configured backlog length. A single oversized call becomes a standing resource cost multiplied by however many live subscribers and however many backlog entries are retained, with no configured limit anywhere actually stopping it.
Problem
POST /notifications accepts any JSON-serializable value in data with no size check at all, even though app/config.py already defines a max_payload_bytes setting (default 32,768 bytes) specifically for this purpose.
Current behavior
Nothing in app/schemas.py's PublishRequest enforces settings.max_payload_bytes -- a request whose data field alone is far larger than the configured limit still returns 200 and lands in Redis unchanged.
Expected behavior
POST /notifications should reject a request whose serialized event + data exceeds settings.max_payload_bytes, before it ever reaches publish_event(), so an oversized payload never gets written to Redis at all.
Steps to reproduce
pytest practice_tickets/tests/test_ticket_02_payload_size_cap.py -vWhy this matters
This is the same class of problem as an unbounded request body, one layer up: the configured backlog length bounds how many entries a user's stream holds, but nothing bounds how large any single entry is allowed to be. A user with a 1000-entry backlog and no payload cap can still end up with a multi-gigabyte backlog from 1000 oversized notifications, each of which also gets rebroadcast in full to every live subscriber via pub/sub.
Suggested approach
Pydantic gives you a hook to run validation logic against a field's serialized form before a PublishRequest is considered valid -- max_payload_bytes from Settings is the number to check against, and it's worth thinking about when that check runs relative to event and data both being set, since the two combined are what actually gets written to Redis by publish_event() in app/publisher.py.
Acceptance criteria
Verification
pytest practice_tickets/tests/test_ticket_02_payload_size_cap.py -v
Hints (0/2)
Try it without hints first — the reading is the exercise.
Working on this ticket
Work on a branch named for the ticket — that's what you'll submit.
Branch off your fork
Sign in to create it automatically, or do it yourself:
$git checkout -b feat/payload-size-capFix it and commit
Meet every acceptance criterion, and add a test that would have caught this.
Push the branch
$git push -u origin feat/payload-size-capSubmit it below
Paste your fork URL and the branch name, with a short write-up of the root cause.
Questions
Ask about anything unclear in the ticket — the maintainer and anyone who has solved it can answer. Please don't post full solutions.
Sign in to ask a question or reply.
Sign inhttp://localhost:8000http://localhost:8001Authorization: Bearer <user_id>X-User-Id: <user_id>curl localhost:8000/healthz
curl -N -H "Authorization: Bearer alice" localhost:8000/notifications/stream
# watch `: heartbeat` lines arrive roughly every 15s with no other traffic
Work the tickets in practice_tickets/tickets/ (ordered easy → hard); each names one dedicated pytest test in practice_tickets/tests/:
pytest practice_tickets/tests/test_ticket_01_heartbeat_default_margin.py -v # a single ticket
./practice_tickets/run_tickets.sh # all 15, clean pass/fail summary
Tickets 14 and 15 need genuinely concurrent, long-lived streams, which the ASGI test transport used everywhere else in this project can't represent -- their tests spin up a real (short-lived) uvicorn server on a loopback socket instead (LiveServer in practice_tickets/tests/conftest.py).
Level 2
Implement a feature
Extend the system within its own patterns.