Architecture
flowchart TB
subgraph Sources
HEVY[Hevy API] ; HAE[Health Auto Export iOS] ; STUDY[Udemy userscript]
end
subgraph Browser
SPA[Web SPA<br/>CloudFront + S3]
end
GW["API Gateway (HTTP API)<br/>throttled 20/40"]
subgraph Lambdas["Lambdas · python3.12 · arm64 · perms-boundary"]
ING[ingest] ; ADV[advice] ; CHAT[chat] ; STATS[stats] ; HSYNC[hevy_sync]
end
DDB[("DynamoDB<br/>lifebot-records")]
S3[("S3 raw archive<br/>versioned · AES256")]
BED["Bedrock<br/>Claude Haiku 4.5"]
SSM["SSM SecureString<br/>/lifebot/*"]
COG[Cognito user pool<br/>PKCE · admin-create-only]
CRON[EventBridge daily]
HEVY & HAE & STUDY -- "x-lifebot-key" --> GW
SPA -- "Cognito JWT" --> GW
SPA -. login .-> COG
GW --> ING & ADV & CHAT & STATS
CRON --> ADV
HSYNC --> DDB
ING --> S3 & DDB
ADV & CHAT --> BED
ADV & CHAT & STATS --> DDB
SSM -.-> ING & ADV & HSYNCAuth model (two tiers)
- Machine ingest —
POST /ingest/{health,hevy,study}andPOST /adviceare gated by a sharedx-lifebot-keyheader (an SSM SecureString), because the callers (Hevy, Health Auto Export, a Udemy userscript) are machines with changing IPs — a shared secret is the right M2M auth, not IP-locking. Ingest is idempotent (de-dupes re-sends). - Browser app —
GET /api/stats,POST /api/goals,POST /api/chatrequire a Cognito JWT (API Gateway JWT authorizer). The user pool is admin-create-only (no public signup), single user, 12-char password policy, PKCE public SPA client, short tokens (id/access 1 h, refresh 30 d). Login via the Cognito hosted UI.
Components
- API Gateway HTTP API (cheaper than REST) — CORS restricted to the app domain, the
CloudFront domain, and
www.udemy.com(the study userscript); throttled (rate 20 / burst 40) to cap Bedrock + DynamoDB cost/DoS. ingest— archives raw JSON to versioned S3, normalizes to daily records in DynamoDB.advice— daily digest (EventBridge) + on-demandPOST /advice; Bedrock Haiku; stores advice.chat—POST /api/chat, Q&A over your recent records via Bedrock (lookback window).stats—GET /api/stats(dashboard) +POST /api/goals(branches on HTTP method).hevy_sync— pulls Hevy workouts into the store.- Storage — DynamoDB
lifebot-records(on-demand, 25 GB free tier); S3 raw archive (versioned, AES256, private). - Frontend — CloudFront + S3 SPA; runtime
config.jstemplated by Terraform.
Security
- Permissions boundary on every Lambda role — a ceiling the deploy user itself cannot
exceed. Each role also gets a least-privilege inline policy scoped to the one DynamoDB
table ARN and only the actions it needs (
ingestwrite-only;chat/statsread; etc.). - Bedrock scoped to the Haiku model/inference-profile ARNs only; KMS Decrypt gated by
a
kms:ViaService = ssmcondition (can only decrypt SSM params). - Secrets in SSM SecureString, never in Terraform state; Cognito password set out-of-band.
- S3 private + versioned + AES256; raw health payloads never logged anywhere world-readable.
- AI privacy — Bedrock/Anthropic do not train on API data (confidential by default), which is why Bedrock Haiku was chosen over an unpaid Gemini tier for health data.
- Full findings + minor items in Bobs-lifebot/security-review.
Cost
Target ≤ £5/mo, typically pennies: HTTP API + Lambda (free tier covers personal volume), DynamoDB on-demand (25 GB free), S3 (pennies), SSM standard (free), Bedrock Haiku (~pennies at ~1 call/day on aggregated data). No always-on DB (RDS/Aurora was explicitly rejected as budget-blowing). Deliberately aggregate to daily/hourly before any AI call — never dump raw HealthKit samples into a prompt.
Related
Compiled from
wiki/projects/Bobs-lifebot/HLD.md · git is the source of truth