From b3b1f7e776eabffe0e1641cf2081be65fadc9e3e Mon Sep 17 00:00:00 2001 From: ashwinimanoj Date: Mon, 18 May 2026 19:26:42 +0530 Subject: [PATCH] docs(shield): add LLD template sample for Bytebite user signup Adds a fully-fleshed sample LLD as a reference for a future Shield LLD-template feature. Uses a fictional food-delivery user-signup feature (Postgres + Redis) so all 14 sections have realistic content. Shape: - 12 always-on sections (Overview, Scope, Module layout, Data model, API contracts, Sequence flows, Error handling, Concurrency, Observability, Performance & scaling, Open questions, Changelog) - 2 promote-on-demand sections (Configuration, Security & privacy) - Stable kebab-case anchors per section + sub-section so plan.json stories can reference them via lld_refs - Mermaid sequence diagrams (light theme via CDN) - Performance & scaling is structured into 8 forced-concrete subsections to prevent vague-prose drift - Changelog ties each LLD edit back to a story ID Co-Authored-By: Claude Opus 4.7 (1M context) --- .../specs/2026-05-18-lld-sample.html | 704 ++++++++++++++++++ 1 file changed, 704 insertions(+) create mode 100644 docs/superpowers/specs/2026-05-18-lld-sample.html diff --git a/docs/superpowers/specs/2026-05-18-lld-sample.html b/docs/superpowers/specs/2026-05-18-lld-sample.html new file mode 100644 index 00000000..7682cb20 --- /dev/null +++ b/docs/superpowers/specs/2026-05-18-lld-sample.html @@ -0,0 +1,704 @@ + + + + +LLD — Bytebite: User Signup + + + + +

LLD — Bytebite: User Signup

+ + + + + + + + + +
Featureuser-signup-20260512
Owner@ashwinimanoj
Statusactive
Linked PRD../prd/1-user-signup/prd.md
Linked plans../plan/1-user-signup/, ../plan/2-email-verification/
Version0.4.0
Last updated2026-05-18
+ +

Table of contents

+
    +
  1. Overview
  2. +
  3. Scope & non-goals
  4. +
  5. Module layout
  6. +
  7. Data model
  8. +
  9. API contracts
  10. +
  11. Sequence flows
  12. +
  13. Error handling
  14. +
  15. Concurrency & state
  16. +
  17. Configuration
  18. +
  19. Observability
  20. +
  21. Security & privacy
  22. +
  23. Performance & scaling
  24. +
  25. Open questions
  26. +
  27. Changelog
  28. +
+ +

1. Overview #overview

+

+ Bytebite is a food-delivery marketplace. This LLD covers the user-signup slice: a new customer + creates an account with email + password, receives a one-time email verification link, and on + click is marked active. The slice owns three endpoints (POST /users, + POST /users/verify, GET /users/me) and writes to one Postgres table + (users) and one Redis namespace (signup:*). +

+

+ This LLD is the design contract for stories in epics E1 Account Creation, + E2 Email Verification, and E3 Anti-abuse. It mirrors the + PRD's Onboarding M1 milestone. +

+ +

2. Scope & non-goals #scope

+

In scope

+ +

Out of scope

+ + +

3. Module layout #module-layout

+

Files touched in this LLD. + new + mod + unchanged +

+
+src/
+├── main.py                          unchanged
+├── db.py                            mod        # add User repository
+├── cache.py                         new        # Redis client + namespaced keys
+├── errors.py                        mod        # add SignupError, OTPError
+├── models/
+│   ├── user.py                      new        # User entity, signup request/response
+│   └── verification.py              new        # EmailVerification entity
+├── services/
+│   ├── signup_service.py            new        # orchestration: validate → hash → write → enqueue mail
+│   └── verification_service.py      new        # token issue + consume
+├── routes/
+│   └── users.py                     new        # POST /users, POST /users/verify, GET /users/me
+└── workers/
+    └── email_dispatcher.py          new        # async send via SendGrid
+
+migrations/
+├── 20260512_create_users.sql        new
+└── 20260513_create_email_verif.sql  new
+
+tests/
+├── test_user_model.py               new
+├── test_signup_service.py           new
+├── test_verification_service.py     new
+└── test_users_routes.py             new
+
+ +

4. Data model #data-model

+

Two tables in Postgres; one Redis namespace for ephemeral state.

+ +

Postgres: users

+ + + + + + + + + + + + + + +
ColumnTypeConstraintsNotes
iduuidPK, default gen_random_uuid()Public-facing identifier
emailcitextNOT NULL, UNIQUECase-insensitive; index on lower(email)
phonetextNULLE.164 format when present
full_nametextNOT NULL, length 1..120Display name
password_hashtextNOT NULLbcrypt $2b$12$...
statusenumpending | active | disabledStarts pending, flips to active on verify
dietary_prefstext[]default '{}'Tags: vegetarian, vegan, halal, …
marketing_opt_inbooleanNOT NULL, default falseExplicit consent only
created_attimestamptzNOT NULL, default now()
last_login_attimestamptzNULLUpdated on successful login
+ +

Postgres: email_verifications

+ + + + + + + + + +
ColumnTypeConstraintsNotes
tokentextPK256-bit random, base64url-encoded
user_iduuidFK → users.id, NOT NULL
expires_attimestamptzNOT NULLNow + OTP_TTL_SECONDS
consumed_attimestamptzNULLSet on first successful verify
created_attimestamptzNOT NULL, default now()
+ +

Redis namespaces

+ + + + + + + + +
Key patternTypeTTLPurpose
signup:ratelimit:ip:{ip}INCR counter3600sCap signup attempts per IP
signup:ratelimit:email:{email_hash}INCR counter3600sCap signups per email (defense against burning addresses)
verify:attempts:{user_id}INCR counter3600sCap bad-token submissions before lockout
session:{token_jti}HASH {user_id, expires_at}JWT_TTL_SECONDSServer-side revocation lookup for JWTs
+ +

5. API contracts #api-contracts

+ +

5.1 POST /users — create user #api-create-user

+

Request

+
+POST /users
+Content-Type: application/json
+
+{
+  "email": "alex@example.com",
+  "full_name": "Alex Patel",
+  "phone": "+919876543210",
+  "password": "hunter2!solid-rosebud",
+  "dietary_prefs": ["vegetarian"],
+  "marketing_opt_in": false
+}
+
+

Response — 201 Created

+
+{
+  "id": "01HQK4G3PE7XYAS2RJ8M5VFW9P",
+  "email": "alex@example.com",
+  "full_name": "Alex Patel",
+  "phone": "+919876543210",
+  "status": "pending",
+  "dietary_prefs": ["vegetarian"],
+  "created_at": "2026-05-18T14:23:45Z",
+  "verification_required": true
+}
+
+

Response — 200 OK (email-enumeration defense)

+

When the email is already in use, the service still returns 200 with the response above + (but id is omitted) and silently triggers a password-reset email instead of a + verification email. This prevents enumeration via response-time or response-code differences.

+

Response — 422 Unprocessable Entity

+
+{
+  "detail": [
+    { "loc": ["body", "password"], "msg": "must be at least 10 characters", "type": "value_error" },
+    { "loc": ["body", "phone"], "msg": "must be E.164 format", "type": "value_error" }
+  ]
+}
+
+

Response — 429 Too Many Requests

+
+{ "detail": "Too many signup attempts. Try again in 23 minutes." }
+
+ +

5.2 POST /users/verify — confirm email #api-verify-user

+

Request

+
+POST /users/verify
+Content-Type: application/json
+
+{ "token": "h7v9_T2nQq1k8nUOaJ-WnH8sCxAa3gqcW-A-pT5gqz0" }
+
+

Response — 200 OK

+
+{
+  "id": "01HQK4G3PE7XYAS2RJ8M5VFW9P",
+  "email": "alex@example.com",
+  "status": "active",
+  "verified_at": "2026-05-18T14:25:10Z"
+}
+
+

Response — 410 Gone (token expired or already consumed)

+
+{ "detail": "Verification link has expired. Request a new one." }
+
+ +

5.3 GET /users/me — current user #api-get-me

+

Requires Authorization: Bearer <jwt>. Returns the same shape as 5.1's 201 + response (minus verification_required) plus last_login_at.

+ +

6. Sequence flows #flows

+ +

6.1 Signup happy path #flow-signup

+
+sequenceDiagram + autonumber + participant C as Client + participant API as FastAPI Route + participant R as Redis + participant S as SignupService + participant PG as Postgres + participant W as Email Worker + + C->>API: POST /users (email, password, ...) + API->>R: INCR signup:ratelimit:ip:{ip} + alt over limit + R-->>API: count > limit + API-->>C: 429 Too Many Requests + else within limit + API->>S: validate + create + S->>S: bcrypt hash (cost 12, ~250ms) + S->>PG: INSERT INTO users RETURNING id + alt duplicate email + PG-->>S: IntegrityError + S->>W: enqueue password-reset email + S-->>API: 200 (no id) — enumeration defense + else inserted + PG-->>S: user_id + S->>PG: INSERT INTO email_verifications (token, ttl) + S->>W: enqueue verification email + S-->>API: created user + API-->>C: 201 Created + end + end +
+ +

6.2 Verify token #flow-verify

+
+sequenceDiagram + autonumber + participant C as Client + participant API as FastAPI Route + participant V as VerificationService + participant PG as Postgres + participant R as Redis + + C->>API: POST /users/verify { token } + API->>V: consume(token) + V->>PG: UPDATE email_verifications SET consumed_at = now()
WHERE token = $1 AND consumed_at IS NULL
AND expires_at > now() RETURNING user_id + alt 0 rows + PG-->>V: no rows + V-->>API: VerificationExpired + API-->>C: 410 Gone + else 1 row + PG-->>V: user_id + V->>PG: UPDATE users SET status = 'active' + V->>R: DEL verify:attempts:{user_id} + V-->>API: active user + API-->>C: 200 OK + end +
+ +

7. Error handling #error-handling

+ + + + + + + + + + +
ErrorStatusBodyRetryable
RequestValidationError422FastAPI envelopeNo — fix input
RateLimitExceeded429{detail, retry_after_seconds}Yes — after backoff
VerificationExpired410{detail: "..."}No — request new link
TooManyVerifyAttempts429{detail, retry_after_seconds}Yes — after backoff
EmailDispatchFailedLogged only; user signup succeedsYes — retried by worker
(unhandled)500{detail: "Internal server error"}Maybe
+ +

8. Concurrency & state #concurrency

+ +

Race: two signups with the same email

+

Two clients send POST /users for alex@example.com within ~10ms. Both + pass validation. Both bcrypt-hash. Both try to INSERT.

+

Defense: CREATE UNIQUE INDEX users_lower_email ON users (lower(email)). + Postgres serializes the writes; the loser gets IntegrityError. The service catches + it and switches to the password-reset email path (see §6.1 step 6). Net effect: exactly one user + row exists; the second caller learns nothing about duplication.

+
+# signup_service.py
+try:
+    user_id = db.insert_user(...)
+    enqueue_verification_email(user_id)
+except IntegrityError as e:
+    if "users_lower_email" in str(e):
+        enqueue_password_reset_email(email)
+    else:
+        raise
+
+ +

Race: token consumed twice

+

User double-clicks the verification link, or the worker retries. Both requests arrive almost + simultaneously with the same token.

+

Defense: single-statement atomic consume — only one UPDATE + returns a row.

+
+UPDATE email_verifications
+   SET consumed_at = now()
+ WHERE token = $1
+   AND consumed_at IS NULL
+   AND expires_at > now()
+RETURNING user_id;
+
+

Second caller's UPDATE matches zero rows → 410 Gone.

+ +

Why not Redis-based locking

+
+ Redis was considered for an email-level mutex on signup. Rejected because the cache layer can + fail open under partition; the DB unique index is the authoritative defense and is cheaper. + Redis is kept for rate limiting only — where eventual loss of state is acceptable. +
+ +

9. Configuration #configuration

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
VariableDefaultNotes
DATABASE_URL— (required)Postgres connection string; pool min 5, max 20.
REDIS_URLredis://localhost:6379/0Rate limiting + session lookups. Service degrades to "no rate limit" if Redis is unreachable.
BCRYPT_COST12OWASP guidance; raise by 1 every ~2 years. Each step ~doubles per-request CPU. Capacity-plan accordingly.
OTP_TTL_SECONDS600Verification-link lifetime. Shorter = tighter security, longer = better UX in cold inboxes.
OTP_MAX_ATTEMPTS5Per-user bad-token submissions before 1-hour lockout.
SIGNUP_RATE_LIMIT_PER_IP_HOUR5Hard cap; tune up for shared-NAT corporate networks via allowlist override.
JWT_SECRET— (required)HS256 HMAC secret; rotated quarterly. Old secret kept in JWT_SECRET_PREVIOUS for grace-period verification.
JWT_TTL_SECONDS86400Access token lifetime; refresh token is 30d.
EMAIL_PROVIDERsendgridAllowed: sendgrid, ses. Plug-in resolver in workers/email_dispatcher.py.
EMAIL_FROM_ADDRESSno-reply@bytebite.ioMust match DKIM-signed domain.
PASSWORD_MIN_LENGTH10Server-enforced. Client widget should mirror this to avoid post-submit surprises.
+ +

10. Observability #observability

+

Structured logs

+ + +

Metrics

+ + +

Traces

+

One span per request named signup.create. Sub-spans: signup.validate, + signup.bcrypt, signup.db_insert, signup.enqueue_email.

+ +

11. Security & privacy #security-privacy

+

Optional section — include when the feature handles PII, introduces new auth/access surface, or changes the threat model. Skip otherwise.

+

Authentication & tokens

+ + +

Email enumeration defense

+ + +

PII handling

+ + +

Threats considered

+ + + + + + + + + +
ThreatMitigation
Credential stuffingRate limit + bcrypt cost + email-enumeration defense
Mass-signup spamPer-IP and per-email-hash rate limits in Redis
Verification-link replaySingle-use tokens with TTL; atomic consume (see §8)
JWT theftShort TTL + server-side revocation list in Redis
Timing attacks on duplicate-email checkConstant-time response padding (see §11 enumeration defense)
+ +

12. Performance & scaling #performance

+

Each subsection below must have a concrete answer. "n/a — <reason>" is the only allowed escape — vague TBDs are not.

+ +

12.1 Expected load #perf-load

+

Steady: 500 signups/min. Peak: 5,000/min (post-marketing burst).

+ +

12.2 Latency SLO #perf-slo

+

p95 < 600 ms · p99 < 1.2 s, measured end-to-end at the load balancer.

+ +

12.3 Bottleneck #perf-bottleneck

+

bcrypt hash (cost 12) — ~250 ms, CPU-bound. Dominates the hot path; everything else is < 10 ms.

+ +

12.4 Latency breakdown #perf-latency

+

Every step on the happy path and its expected duration. The total must fit under §12.2's SLO with headroom. Estimates at design time; replaced by observed values once the feature ships.

+ + + + + + + + + + + +
StepExpected latencyNotes
Pydantic validation< 1 ms
Redis INCR (rate limit)~1 msSame-AZ Redis
bcrypt hash~250 msCost 12 — see §12.3
INSERT users~5 msUnique-index lookup + insert
INSERT email_verifications~3 ms
Enqueue email~2 msPush to Redis queue; worker sends async
Total (p95)~280 msComfortably under the 600 ms SLO
+ +

12.5 Capacity per unit #perf-capacity

+

One pod (4 vCPU) sustains ~16 signups/sec. Throughput is gated by bcrypt CPU, so capacity scales linearly with vCPU count.

+ +

12.6 Scale-out lever #perf-scaleout

+ + +

12.7 Caches #perf-caches

+ + + + + + + +
KeyStoreTTLWhy
signup:ratelimit:*Redis1 hSpam / enumeration defense; degrade-open on Redis failure (acceptable)
session:{jti}RedisJWT_TTL_SECONDSServer-side JWT revocation lookup; hit-ratio target > 99%
User profile rowsNot cached. Read QPS too low to justify the invalidation complexity. Revisit at > 500 read QPS.
+ +

12.8 Degradation behavior #perf-degradation

+ + +

13. Open questions #open-questions

+ + + + + + + + + + + + + + + + + + + + + + + + + +
#QuestionOptionsOwnerResolve by
Q1Should verification-link clicks log the user in automatically?A) yes, return JWT; B) no, redirect to login@productleadbefore E2-S2 starts
Q2Is column-level pgcrypto enough for PII, or do we need full-disk + column encryption?A) column-only (current); B) both (CIS recommendation)@security-teambefore pre-launch security review
Q3Should we permit signup from disposable-email domains?A) allow; B) reject via maintained blocklist; C) tag-and-allow for fraud-scoring@trust-and-safetybefore public launch
+ +

14. Changelog #changelog

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
DateStorySections touchedChange
2026-05-08#overview, #scopeInitial LLD scaffold from PRD
2026-05-10E1-S1#data-model, #api-create-userLocked users schema and POST /users contract
2026-05-12E1-S2#concurrency, #security-privacyAdded email-enumeration defense; documented duplicate-email race
2026-05-15E2-S1#data-model, #api-verify-user, #flow-verifyAdded email_verifications table and atomic consume statement
2026-05-17E3-S1#configuration, #performanceAdded Redis rate-limit keys and per-IP/per-email caps
2026-05-18E3-S2#security-privacy, #observabilityHashed-email logging; added users_signup_total metric
+ + + + + + + +