engineering/BOOTSTRAP.mdEngineering — MVP track~8 min read

Day-One Bootstrap

From empty laptop to a running, tested skeleton. Everything here uses synthetic data only — no BAA is needed until real PHI flows, which is months away. Target for the very first working session: make up && make seed && make test works and CI is green.

Accounts & tools checklist

ItemNotes
GitHub org + private repo travi-platformBranch protection on main; PRs only; CI required. Repo name ≠ the public hub.
AWS account (dev)Start with ONE dev account; add stage/prod accounts when Terraform is stable (the three-account layout is ADR-005's end state, not day one's).
AWS Bedrock model accessRequest Claude model access in the dev region on day 1 — approval can take days. Pin the model ID in config; temperature 0.
Twilio trial + VerifyTrial account for dev (verified numbers only). SES sandbox for email. BAAs come later with production.
Local: Docker, Python 3.12, uv, Node 20, Terraformuv for env + lockfile; Node for the PWA workspace; direnv is nice-to-have.
Sentry (free tier)Wire the PHI-scrub config from the start even on synthetic data — it must be boring by pilot.

Repo layout (matches platform §4.1)

travi-platform/
├── app/
│   ├── main.py                  # FastAPI factory; mounts every module router
│   ├── core/                    # settings, db, ehr_adapter interface, llm_gateway,
│   │                            #   outbox drainer, config client (10 s cache)
│   └── modules/
│       ├── identity/            # api.py (router) + service.py (facade) + internal/
│       ├── consent/  ingestion/  compiler/  episode/  medication/
│       ├── scheduling/  notification/  brief/  agents/  admin/
│       ├── analytics/
│       └── audit/               # own schema; INSERT-only role; emit() facade
├── pwa/                         # React PWA (patient/caregiver/clinician surfaces)
├── fakeehr/                     # the fixture EHR service (see below)
├── infra/                       # Terraform (dev first)
├── tests/                       # unit / contract / property / integration / e2e
│   └── golden/                  # GS-01…GS-12 scenario harnesses + fixtures
├── corpus/                      # synthetic discharge docs + labels (never PHI)
├── importlinter.toml            # module-boundary contracts — CI fails deep imports
├── docker-compose.yml  Makefile  pyproject.toml

Two structural rules CI enforces from day 1: cross-module calls go through service.py facades only (import-linter contract per module), and nothing outside app/modules/episode imports the task-state writers — the single-writer rule as a build failure, not a convention.

docker-compose (the whole dev world)

services:
  db:        postgres:16          # + a second logical db for the audit schema role split
  redis:     redis:7              # celery broker + config/kill-switch cache
  s3:        localstack           # documents + brief PDFs + (later) audit archive
  mail:      mailpit              # catches all SES email locally (web UI :8025)
  fakeehr:   ./fakeehr            # the hospital, in a box (below)
  api:       ./app  (reload)      # FastAPI on :8000
  worker:    ./app  celery        # default + notifications queues
  pwa:       ./pwa  (vite dev)    # :5173, proxied to api

SMS in dev: a console/mailpit-backed fake Twilio client behind the same interface as the real one — activation links print to the compose log and land in mailpit; flipping to real Twilio is config, not code.

FakeEHR — the hospital in a box

A tiny FastAPI service implementing the same ehr_adapter surface the Epic adapter will: FHIR-ish Patient, Encounter, MedicationRequest, DocumentReference endpoints serving the synthetic corpus PDFs, plus two control endpoints that make demos and tests deterministic: POST /control/discharge?persona=GS-01 (emits the ADT A03 + documents for a persona) and POST /control/admit (mid-episode readmission for GS-08). Build it before any real module — every phase of the Build Plan runs against it, and the delivery plan's biggest schedule risk (Epic paperwork) stops blocking development entirely.

Synthetic personas = the golden scenarios

Seed data and test fixtures are the same twelve people, named for the scenario they exercise (testing §17.3): GS-01 "Dorothy" clean discharge (3 new meds, 1 stopped, 2 follow-ups) · GS-02 "Frank" amended summary supersedes · GS-03 "Rosa" AVS-vs-order dose conflict · GS-04 "Earl" cannot_do(cost) on a critical med · GS-05 "Mae" pharmacy phone confirmation · GS-06 "Walter" stale brief · GS-07 "Gloria" caregiver invite/revoke · GS-08 "Hank" readmitted day 9 · GS-09 "Vera" silent 48 h · GS-10 "Sam" post-visit plan update · GS-11 "Nina" red-flag safety path · GS-12 "Otis" prompt-injection document (a discharge PDF that tries to jailbreak the extractor — Gate 5's span proof is the defense on trial). make seed loads all twelve; make gs S=03 replays one end-to-end.

Makefile targets

make up          # compose up + migrate + healthcheck
make seed        # load the 12 personas into FakeEHR + db
make gs S=04     # run one golden scenario end-to-end
make test        # unit + property + contract (the PR gate)
make bench       # extraction benchmark vs corpus/ labels (field-level P/R table)
make compress    # day-compression: run a full 30-day episode at 1 day/minute
make fmt lint    # ruff + import-linter + tsc for the PWA

make compress matters more than it looks: episode timers (48 h silence, T−24 h brief, day-30 close) must read a clock service injected everywhere — never datetime.now() inline — so tests and demos can run a month in minutes. Retrofitting an injectable clock later is misery; do it on day one.

CI pipeline (GitHub Actions, mirrors testing §17.1)

JobGate
lint: ruff + import-linter + tsc --noEmitPR
unit + property (Hypothesis state machines) — coverage ≥85% on modules/PR
contract: schemathesis vs generated OpenAPI + authz matrix probesPR
integration: compose Postgres/Redis/FakeEHR; outbox ≤60 s suppression simPR
golden: GS suite; extraction bench vs corpus labelsnightly + release
e2e: Playwright activation/Today/med/brief + axe WCAG 2.2 AA scanrelease

Config & secrets hygiene

.env for local only, never committed; AWS Secrets Manager once deployed. Non-negotiables that live in config from day 1: BEDROCK_MODEL_ID (pinned, never "latest"), LLM_TEMPERATURE=0, BRIEF_LEAD_HOURS=24, quiet-hour defaults, kill-switch cache TTL 10 s, TIME_SOURCE=real|compressed. Rule for the repo: synthetic data may be committed (corpus/), credentials never, and there is no such thing as "temporary" real patient data in dev.

The first five PRs

A concrete on-ramp that leaves CI green after each merge: PR-1 repo scaffold + compose + Makefile + CI skeleton (a failing-then-fixed import-linter test proves the boundary gate). PR-2 core: settings, db/session, clock service, migration baseline. PR-3 audit module complete + its property tests (everything after this emits). PR-4 FakeEHR with GS-01 persona + the ehr_adapter interface + contract test. PR-5 episode tables + the state-machine skeleton with the five-element transition contract and its first Hypothesis suite. From there, the Build Plan phases take over — and each module you open starts by pasting its module brief + the overview's hard rules into your coding agent's context, exactly as the overview prescribes.