| <article class="doc" id="doc-reference-platform" aria-hidden="true"><div class="strip"><span class="path">reference/platform.md</span><span class="tag">Engineering, reference</span><span class="meta">~7 min read</span></div> |
| <h1>Platform: Architecture, Stack, Cloud & Actors</h1> |
| <h1>4. System Architecture</h1> |
| <p><img alt="Figure 1, MVP system architecture. One deployable API, one async plane, one canonical store." loading="lazy" src="assets/img/platform-architecture.png"/> |
| <em>Figure 1, MVP system architecture. One deployable API, one async plane, one canonical store.</em></p> |
| <h2 id="doc-reference-platform--h1">4.1 Module Inventory (Monolith Internal Boundaries)</h2> |
| <p>Each module is a Python package under <code>app/modules/</code> with a public interface (<code>api.py</code> router + <code>service.py</code> facade) and private internals. Cross-module calls go through service facades only; <code>import-linter</code> contracts in CI fail the build on any deeper import. Modules share the database but own their tables (write access enforced by convention + code review; the <code>audit</code> schema is enforced by a separate INSERT-only DB role).</p> |
| <div class="tbl-wrap"><table class="col-table compact"><colgroup><col class="col-auto"/><col class="col-lg"/><col class="col-auto"/></colgroup> |
| <thead> |
| <tr> |
| <th>Module</th> |
| <th>Responsibility</th> |
| <th>Key Requirements Owned</th> |
| </tr> |
| </thead> |
| <tbody> |
| <tr> |
| <td data-label="Module"><code>identity</code></td> |
| <td data-label="Responsibility">Activation tokens, patient/caregiver/clinician sessions, grants, step-up, recovery, revocation lists.</td> |
| <td data-label="Key Requirements Owned">ENR-003/004/005, CLN-001, CGV-001/004</td> |
| </tr> |
| <tr> |
| <td data-label="Module"><code>consent</code></td> |
| <td data-label="Responsibility">Consent records, versions, scopes (program, caregiver, recording-optional), withdrawal cascade.</td> |
| <td data-label="Key Requirements Owned">SEC-010, CGV-007</td> |
| </tr> |
| <tr> |
| <td data-label="Module"><code>ingestion</code></td> |
| <td data-label="Responsibility">EHR adapter, ADT listener, FHIR client, document fingerprinting, source versioning.</td> |
| <td data-label="Key Requirements Owned">INT-001, DPC-001</td> |
| </tr> |
| <tr> |
| <td data-label="Module"><code>compiler</code></td> |
| <td data-label="Responsibility">Discharge plan compiler + seven-gate LLM pipeline; extracted-fact publication.</td> |
| <td data-label="Key Requirements Owned">DPC-003/004/007/008, DQS-004/011</td> |
| </tr> |
| <tr> |
| <td data-label="Module"><code>episode</code></td> |
| <td data-label="Responsibility">Episode & Universal Task state machines, transitions, idempotency, prioritization engine.</td> |
| <td data-label="Key Requirements Owned">PAT-001/003/005, ENR-001/002</td> |
| </tr> |
| <tr> |
| <td data-label="Module"><code>medication</code></td> |
| <td data-label="Responsibility">Medication plan items, status events (8 states), barriers, verification labeling.</td> |
| <td data-label="Key Requirements Owned">MED-003/005/006/010</td> |
| </tr> |
| <tr> |
| <td data-label="Module"><code>scheduling</code></td> |
| <td data-label="Responsibility">Appointment requirements, appointments, matching, assisted-scheduling flows, result completion.</td> |
| <td data-label="Key Requirements Owned">APT-001/004/010</td> |
| </tr> |
| <tr> |
| <td data-label="Module"><code>notification</code></td> |
| <td data-label="Responsibility">Channel selection, escalation ladders, quiet hours, suppression outbox, templates, silent-deterioration detector.</td> |
| <td data-label="Key Requirements Owned">ENG-002/004/012, EDU-002/007</td> |
| </tr> |
| <tr> |
| <td data-label="Module"><code>brief</code></td> |
| <td data-label="Responsibility">Brief snapshots, precompute triggers, staleness checks, post-visit update loop.</td> |
| <td data-label="Key Requirements Owned">VIS-002/005/007, CLN-004/008</td> |
| </tr> |
| <tr> |
| <td data-label="Module"><code>agents</code></td> |
| <td data-label="Responsibility">Agent orchestrator, policy envelopes, tool registry, evidence records, kill switches.</td> |
| <td data-label="Key Requirements Owned">AUT-001/006/009</td> |
| </tr> |
| <tr> |
| <td data-label="Module"><code>admin</code></td> |
| <td data-label="Responsibility">Tenant/cohort/config management, versioned publication, RBAC administration.</td> |
| <td data-label="Key Requirements Owned">ADM-004 config side</td> |
| </tr> |
| <tr> |
| <td data-label="Module"><code>analytics</code></td> |
| <td data-label="Responsibility">Event projections, denominators, execution & outcome reports, coverage flags.</td> |
| <td data-label="Key Requirements Owned">ADM-004/007</td> |
| </tr> |
| <tr> |
| <td data-label="Module"><code>audit</code></td> |
| <td data-label="Responsibility">Append-only audit writer + query API; S3 WORM archiver.</td> |
| <td data-label="Key Requirements Owned">SEC-003, DQS-001</td> |
| </tr> |
| </tbody> |
| </table></div> |
| <h2 id="doc-reference-platform--h2">4.2 Runtime Topology</h2> |
| <div class="tbl-wrap"><table class="col-table compact"><colgroup><col class="col-auto"/><col class="col-auto"/><col class="col-md"/></colgroup> |
| <thead> |
| <tr> |
| <th>Process</th> |
| <th>Scale (pilot)</th> |
| <th>Notes</th> |
| </tr> |
| </thead> |
| <tbody> |
| <tr> |
| <td data-label="Process"><code>api</code></td> |
| <td data-label="Scale (pilot)">2–4 tasks, 1 vCPU/2 GB</td> |
| <td data-label="Notes">FastAPI + Uvicorn behind ALB; stateless; readiness = DB+Redis ping.</td> |
| </tr> |
| <tr> |
| <td data-label="Process"><code>worker-default</code></td> |
| <td data-label="Scale (pilot)">2 tasks</td> |
| <td data-label="Notes">Celery queues: <code>episode</code>, <code>ingest</code>, <code>analytics</code>. Prefetch 1, acks-late, visibility 300 s.</td> |
| </tr> |
| <tr> |
| <td data-label="Process"><code>worker-llm</code></td> |
| <td data-label="Scale (pilot)">1–2 tasks</td> |
| <td data-label="Notes">Queue <code>llm</code>; concurrency 4; per-task Bedrock budget caps (§11.7); isolates latency.</td> |
| </tr> |
| <tr> |
| <td data-label="Process"><code>worker-notify</code></td> |
| <td data-label="Scale (pilot)">2 tasks</td> |
| <td data-label="Notes">Queues <code>notify</code>, <code>outbox</code>; drains suppression outbox every 5 s to meet the 60 s SLA.</td> |
| </tr> |
| <tr> |
| <td data-label="Process"><code>beat</code></td> |
| <td data-label="Scale (pilot)">1 task (singleton)</td> |
| <td data-label="Notes">Celery beat: schedules in §16.5. Leader lock in Redis.</td> |
| </tr> |
| <tr> |
| <td data-label="Process"><code>migrator</code></td> |
| <td data-label="Scale (pilot)">on deploy</td> |
| <td data-label="Notes">Alembic <code>upgrade head</code> as ECS one-off task; deploy gate.</td> |
| </tr> |
| </tbody> |
| </table></div> |
| <h2 id="doc-reference-platform--h3">4.3 Technology Stack (pinned)</h2> |
| <div class="tbl-wrap"><table class="col-table compact"><colgroup><col class="col-auto"/><col class="col-md"/><col class="col-md"/></colgroup> |
| <thead> |
| <tr> |
| <th>Layer</th> |
| <th>Choice (version)</th> |
| <th>Notes</th> |
| </tr> |
| </thead> |
| <tbody> |
| <tr> |
| <td data-label="Layer">Language / API</td> |
| <td data-label="Choice (version)">Python 3.12 · FastAPI 0.115 · Uvicorn 0.30</td> |
| <td data-label="Notes">OpenAPI generated; <code>/v1</code> prefix; Pydantic v2 models shared with workers.</td> |
| </tr> |
| <tr> |
| <td data-label="Layer">ORM / migrations</td> |
| <td data-label="Choice (version)">SQLAlchemy 2.0.32 · Alembic 1.13</td> |
| <td data-label="Notes">One migration lineage; no auto-generated DDL in prod without review.</td> |
| </tr> |
| <tr> |
| <td data-label="Layer">Async jobs</td> |
| <td data-label="Choice (version)">Celery 5.4 · Redis 7.2</td> |
| <td data-label="Notes">Priorities via dedicated queues, not priority flag.</td> |
| </tr> |
| <tr> |
| <td data-label="Layer">Frontend</td> |
| <td data-label="Choice (version)">React 18.3 · TypeScript 5.5 · Vite 5 · Workbox 7</td> |
| <td data-label="Notes">PWA manifest; bundle budget 250 KB gz initial for patient routes.</td> |
| </tr> |
| <tr> |
| <td data-label="Layer">UI kit</td> |
| <td data-label="Choice (version)">Radix UI + Tailwind 3.4</td> |
| <td data-label="Notes">44 px min touch targets; WCAG 2.2 AA tokens; EN/ES i18n via i18next.</td> |
| </tr> |
| <tr> |
| <td data-label="Layer">LLM</td> |
| <td data-label="Choice (version)">Bedrock: <code>anthropic.claude-sonnet</code> pinned model ID; Titan embed v2</td> |
| <td data-label="Notes">IDs live in model registry table (§11.6), never in code.</td> |
| </tr> |
| <tr> |
| <td data-label="Layer">Telephony</td> |
| <td data-label="Choice (version)">Twilio SMS + Voice (Studio flows)</td> |
| <td data-label="Notes">BAA signed; status callbacks → <code>/webhooks/twilio</code>.</td> |
| </tr> |
| <tr> |
| <td data-label="Layer">Email</td> |
| <td data-label="Choice (version)">AWS SES</td> |
| <td data-label="Notes">Transactional only; DKIM/DMARC enforced.</td> |
| </tr> |
| <tr> |
| <td data-label="Layer">Observability</td> |
| <td data-label="Choice (version)">OpenTelemetry SDK → ADOT collector → CloudWatch; Sentry (PHI-scrubbed)</td> |
| <td data-label="Notes">Trace ID propagated into Celery headers and audit rows.</td> |
| </tr> |
| <tr> |
| <td data-label="Layer">Testing</td> |
| <td data-label="Choice (version)">pytest 8 · Playwright 1.45 · schemathesis · Locust</td> |
| <td data-label="Notes">Layer mapping in §17.1.</td> |
| </tr> |
| </tbody> |
| </table></div> |
| <h2 id="doc-reference-platform--h4">4.4 Repository & Conventions</h2> |
| <pre><code>travi ai agent/ # single monorepo |
| app/ # FastAPI monolith |
| modules/{identity,consent,ingestion,compiler,episode,medication, |
| scheduling,notification,brief,agents,admin,analytics,audit}/ |
| api.py service.py models.py schemas.py tasks.py tests/ |
| core/ # settings, db, security, llm_gateway, ehr_adapter, outbox |
| main.py |
| web/ # React PWA (patient/caregiver/clinician routes) |
| infra/ # Terraform: modules/ + envs/{dev,stage,prod} |
| ops/ # runbooks, dashboards-as-code, oncall |
| docs/adr/ # ADR-001..018 as markdown, this doc's source |
| .github/workflows/ # ci.yml, deploy.yml, nightly-golden.yml |
| </code></pre> |
| <ul> |
| <li><strong>Branching:</strong> trunk-based; short-lived feature branches; squash merge; Conventional Commits; release tags <code>v0.x.y</code>.</li> |
| <li><strong>Quality gates (CI-blocking):</strong> ruff + mypy --strict, eslint + tsc, import-linter contracts, pytest ≥85% line coverage on <code>modules/</code>, schemathesis against OpenAPI, Playwright smoke, Trivy image scan (no HIGH/CRITICAL), tfsec.</li> |
| <li><strong>Code review:</strong> 1 approval; 2 for <code>identity</code>, <code>compiler</code> gates, <code>audit</code>, Terraform, or any migration touching PHI tables.</li> |
| <li><strong>PHI hygiene:</strong> no PHI in logs, traces, Sentry, commit messages, or fixtures, synthetic data only outside prod (§15.6).</li> |
| </ul> |
| <h1>5. Environments, Cloud & Delivery Infrastructure</h1> |
| <h2 id="doc-reference-platform--h5">5.1 Accounts & Environments</h2> |
| <div class="tbl-wrap"><table class="col-table compact"><colgroup><col class="col-auto"/><col class="col-auto"/><col class="col-lg"/></colgroup> |
| <thead> |
| <tr> |
| <th>Env</th> |
| <th>AWS Account</th> |
| <th>Purpose / Data Policy</th> |
| </tr> |
| </thead> |
| <tbody> |
| <tr> |
| <td data-label="Env">dev</td> |
| <td data-label="AWS Account"><code>tai-dev</code></td> |
| <td data-label="Purpose / Data Policy">Ephemeral stacks per PR optional; synthetic data only; relaxed sizes; nightly teardown of previews.</td> |
| </tr> |
| <tr> |
| <td data-label="Env">stage</td> |
| <td data-label="AWS Account"><code>tai-stage</code></td> |
| <td data-label="Purpose / Data Policy">Production-shaped; Epic <strong>sandbox</strong> connectivity; synthetic + partner test patients only; load & DR drills here.</td> |
| </tr> |
| <tr> |
| <td data-label="Env">prod</td> |
| <td data-label="AWS Account"><code>tai-prod</code></td> |
| <td data-label="Purpose / Data Policy">Live PHI. Change only via pipeline; console read-only except break-glass role (audited).</td> |
| </tr> |
| </tbody> |
| </table></div> |
| <h2 id="doc-reference-platform--h6">5.2 Network & Core Services</h2> |
| <ul> |
| <li><strong>VPC</strong> per env: 3 AZs; public subnets = ALB + NAT only; private subnets = ECS, RDS, ElastiCache; no public IPs on tasks; VPC endpoints for S3, ECR, Secrets Manager, Bedrock, CloudWatch.</li> |
| <li><strong>Hospital connectivity:</strong> site-to-site IPsec VPN (or partner-preferred private link) terminating in a dedicated <code>integration</code> subnet; ADT listener reachable only from partner interface-engine CIDR; all inbound mTLS where the engine supports it.</li> |
| <li><strong>Edge:</strong> CloudFront serves the static PWA bundle (no PHI cached; <code>Cache-Control: no-store</code> on all API responses); AWS WAF managed rules + rate rules (<code>/v1/patient/activate</code>: 10/min/IP) in front of ALB.</li> |
| <li><strong>Data:</strong> RDS PostgreSQL 16 Multi-AZ (db.m7g.large pilot), PITR 15-min granularity, 35-day backups; ElastiCache Redis 7 (cluster mode off, multi-AZ); S3 buckets <code>tai-{env}-sources</code>, <code>-briefs</code>, <code>-exports</code>, <code>-audit-archive</code> (Object Lock compliance 6 y on audit), all SSE-KMS with per-purpose CMKs, versioning + access logs on.</li> |
| <li><strong>Secrets:</strong> AWS Secrets Manager; 90-day rotation for DB/Twilio/Epic client credentials; no secrets in env files or Terraform state (state in S3 + DynamoDB lock, KMS-encrypted).</li> |
| <li><strong>IAM:</strong> task roles per process (api / workers / migrator); Bedrock invoke restricted to registry model IDs; deny <code>s3:DeleteObject</code> on audit archive to all runtime roles.</li> |
| </ul> |
| <h2 id="doc-reference-platform--h7">5.3 CI/CD</h2> |
| <ol> |
| <li>PR → CI gates (§4.4) on GitHub Actions; preview deploy to dev on label.</li> |
| <li>Merge to <code>main</code> → build multi-arch image → push ECR → auto-deploy <strong>stage</strong> (migrator → blue/green ECS via CodeDeploy, 10% canary 10 min, auto-rollback on alarm).</li> |
| <li>Manual approval (CTO or delegate) → <strong>prod</strong> deploy, same shape; release notes auto-generated from Conventional Commits.</li> |
| <li>Nightly: golden-scenario suite + extraction benchmark against pinned models; regression fails page the on-call and block the next prod deploy.</li> |
| </ol> |
| <h2 id="doc-reference-platform--h8">5.4 Resilience, DR & Retention</h2> |
| <div class="tbl-wrap"><table class="col-table compact"><colgroup><col class="col-auto"/><col class="col-lg"/></colgroup> |
| <thead> |
| <tr> |
| <th>Item</th> |
| <th>Commitment / Mechanism</th> |
| </tr> |
| </thead> |
| <tbody> |
| <tr> |
| <td data-label="Item">Availability target</td> |
| <td data-label="Commitment / Mechanism">≥99.9% monthly, Multi-AZ RDS/Redis, ≥2 tasks/service across AZs, ALB health checks, graceful drains.</td> |
| </tr> |
| <tr> |
| <td data-label="Item">RPO / RTO</td> |
| <td data-label="Commitment / Mechanism">RPO ≤15 min (PITR + Redis is rebuildable); RTO ≤4 h via Terraform re-provision runbook; quarterly restore test in stage is a release-blocking control.</td> |
| </tr> |
| <tr> |
| <td data-label="Item">Degradation modes</td> |
| <td data-label="Commitment / Mechanism">Bedrock down → pipeline pauses at Gate 2, tasks stay <code>pending_extraction</code>, ops alert (no unvalidated publishing). Twilio down → channel ladder skips to next channel; safety-tier failures page. Epic down → ingestion retries with backoff; episodes continue on last-known plan.</td> |
| </tr> |
| <tr> |
| <td data-label="Item">Data retention</td> |
| <td data-label="Commitment / Mechanism">PHI: contract term + 6 y (audit) unless partner policy stricter; Twilio/SES message bodies not stored at vendor beyond delivery (config); LLM: Bedrock zero-retention, no training use (SEC-010); voice recordings: not stored in MVP.</td> |
| </tr> |
| <tr> |
| <td data-label="Item">Environments parity</td> |
| <td data-label="Commitment / Mechanism">Stage = prod Terraform with size overrides only; drift detection nightly (<code>terraform plan</code> must be empty).</td> |
| </tr> |
| </tbody> |
| </table></div> |
| <h1>2. Scope & Requirements Baseline</h1> |
| <p>The functional baseline is the Product Requirements Document. All requirement IDs referenced in this document (e.g., <code>MED-006</code>, <code>ENG-012</code>) are normative and their acceptance criteria are the test oracle. Section 20 provides the full requirement-to-component traceability matrix; no MVP requirement is unowned. Where the PRD says “configurable,” this specification names the configuration key, its default value, and its owner.</p> |
| <h2 id="doc-reference-platform--h9">2.1 Actors and Trust Levels</h2> |
| <div class="tbl-wrap"><table class="col-table compact"><colgroup><col class="col-auto"/><col class="col-md"/><col class="col-lg"/></colgroup> |
| <thead> |
| <tr> |
| <th>Actor</th> |
| <th>Authentication</th> |
| <th>Trust / Authorization Scope</th> |
| </tr> |
| </thead> |
| <tbody> |
| <tr> |
| <td data-label="Actor">Patient</td> |
| <td data-label="Authentication">Activation token → session JWT (Section 9.1)</td> |
| <td data-label="Trust / Authorization Scope">Own episode only. Can report status, respond to tasks, manage consent & caregivers.</td> |
| </tr> |
| <tr> |
| <td data-label="Actor">Caregiver</td> |
| <td data-label="Authentication">Own invitation token → own session JWT</td> |
| <td data-label="Trust / Authorization Scope">Patient-granted scope on one episode; read + report; never sees other episodes; revocable instantly.</td> |
| </tr> |
| <tr> |
| <td data-label="Actor">Clinician</td> |
| <td data-label="Authentication">Magic link + email OTP; step-up OTP for confirmations</td> |
| <td data-label="Trust / Authorization Scope">One patient, one episode, time-boxed grant. Read brief; confirm/clarify material items with step-up.</td> |
| </tr> |
| <tr> |
| <td data-label="Actor">Hospital admin</td> |
| <td data-label="Authentication">Design-partner IdP via OIDC SSO + MFA</td> |
| <td data-label="Trust / Authorization Scope">Tenant-scoped configuration, cohorts, reports, kill switches per RBAC matrix (Section 9.5).</td> |
| </tr> |
| <tr> |
| <td data-label="Actor">Travi AI Agent operator</td> |
| <td data-label="Authentication">Internal IdP (Google Workspace OIDC) + MFA</td> |
| <td data-label="Trust / Authorization Scope">Break-glass PHI access is separately authorized, reasoned, time-boxed, and audited.</td> |
| </tr> |
| <tr> |
| <td data-label="Actor">System agents</td> |
| <td data-label="Authentication">Service identity (IAM role) + policy envelope</td> |
| <td data-label="Trust / Authorization Scope">Least-privilege tool allowlists; can never transition clinical state directly (Section 12).</td> |
| </tr> |
| </tbody> |
| </table></div> |
| <h2 id="doc-reference-platform--h10">2.2 Configuration Ownership Model</h2> |
| <p>Every tunable named in this document lives in the <code>tenant_config</code> service (Section 16.4) with three tiers: <strong>platform defaults</strong> (owned by Travi AI Agent engineering, changeable by release), <strong>tenant policy</strong> (owned by the design partner, changeable via admin console with versioning per ADM requirements), and <strong>cohort overrides</strong> (owned by the clinical lead). Configuration changes never retroactively alter active episodes; a new <code>config_version</code> applies only to episodes created after publication, satisfying the enterprise immutability constraint.</p> |
| <div class="pn"><a class="pn-prev" href="#"></a><a class="pn-next" href="#"></a></div></article> |
|
|