Skip to content

ADR Index

Ankit Upadhyay edited this page Aug 13, 2026 · 1 revision

ADR index

This page lists every architecture decision record with its decision in one line, and describes the process for adding one. It is for anyone who wants to know why something is the way it is before proposing changing it.

The repository is canonical. ADRs live in docs/adr/. If this page and a record in the repository disagree, the repository is right and this page is stale.

The records

ADR Title Status The decision, in one line
0001 Monorepo with pnpm, Turborepo, and Node 22 Accepted One repository with pnpm 10 workspaces, Turborepo orchestration, Node 22, Vitest, and ESLint 9 flat config, so a schema, mapper, API, and UI change lands atomically in one pull request.
0002 Relational Postgres as source of truth, FHIR R4 at the API boundary Accepted A normalised PostgreSQL schema managed by Prisma is the single source of truth; FHIR R4 serialisation happens at the API edge, and every mapped resource ships with round-trip tests or it does not merge.
0003 Branch model: main plus dev with a single aggregate check Accepted Two branches, main for release and dev for integration, with CI Required as the only required status check and protection implemented through rulesets rather than classic branch protection.
0004 No ML runtime in the core deployment Accepted No machine-learning runtime inside the core deployment; if it ever ships it arrives as an optional out-of-process adapter behind the existing partner-adapter seam, never on the request path.

ADR-0004 is on the feat/emr-app branch and has not merged to dev. The first three are on dev.

Why each one, briefly

0001 rejected two alternatives. A different orchestrator with more built-in generators bought little for a repository this size and diverged from the team's operational experience. One repository per application was rejected because the FHIR mapping layer and the Prisma schema change in lockstep with their consumers, so versioned publishing between repositories would add release overhead and drift risk with no compensating benefit.

The costs were recorded rather than hidden: the repository grows monotonically and CI must rely on caching and filtering to stay fast, and a single Node version policy means a runtime upgrade touches everything at once.

0002 is the decision that shapes the most code. FHIR compliance is a property of the API surface, because certification programmes and integrations test what a server returns over HTTP, not how bytes are laid out on disk. Meanwhile the application needs joins, constraints, and narrow projections. So storage is relational and FHIR lives at the edge.

Two alternatives were rejected. Storing FHIR resources as JSON documents and indexing for FHIR search would mean rebuilding search machinery that mature servers took years to harden, taxing every screen with parsing, and degrading typed access to loosely typed navigation. Delegating to an external reference server would drag a heavyweight second runtime into an otherwise single-language stack and double the persistence surface.

The accepted costs are named plainly: the mapping layer is a permanent tax; domain and FHIR can drift, bounded but not eliminated by round-trip tests; and generic FHIR search has to be implemented parameter by parameter.

0003 exists because required-check lists that name individual jobs go stale the moment jobs are renamed or resharded. One aggregate check means CI can evolve without touching repository settings, and a green CI Required has a single unambiguous meaning. The record also names the risk: the aggregate is a single point of truth, so it must fail closed, treating a cancelled leg as a failure rather than letting it through.

0004 is the most detailed record and the most useful to read in full. Four constraints decided it. The runtime shape of the available toolkits is a second heavyweight runtime, which is the same objection ADR-0002 raised against an external server. Model-weight licence provenance is unresolved for several widely published domain-adapted models, which is structurally the same problem as vendoring terminology content, and not vendoring licensed content is a differentiator this project intends to keep. Deployability puts the useful models at workstation class, which breaks the install-time target. And the regulatory line matters: extraction that displays information already present in a source document is not a device function, while ranking findings by clinical risk, inferring anything not stated, auto-committing without review, or directing interpretation at a patient rather than a clinician all move toward device classification.

It also records what happens instead. Plain-language explanation of clinical terms comes from a curated code-to-plain-language mapping keyed on codes already stored. Search over notes uses Postgres full-text search. Extraction from outside records begins with structured import and a human reconciliation surface.

One rejected alternative in that record is worth repeating, because it sounds appealing: using a model to guard against real patient data entering the repository. It was rejected because correct synthetic data is deliberately shaped like real patient data, so precision approaches zero and the gate would be muted within a week. Provenance controls and deterministic secret scanning are the right tools.

The process

  • ADRs are numbered sequentially as NNNN-short-title.md. Numbers are never reused, even for a rejected or superseded record.
  • Start from docs/adr/template.md.
  • A new ADR enters as Proposed in its pull request and becomes Accepted when that pull request merges to dev.
  • Decisions are immutable history. When one is reversed or replaced, do not delete or rewrite the old record. Mark it superseded, link forward, and write a new one.
  • If a code change reverses or significantly extends a recorded decision, the pull request must include the corresponding ADR.
Status Meaning
Proposed Under discussion in an open pull request
Accepted Merged. This is how we do it
Superseded Replaced by a later ADR, which it must link to
Rejected Considered and turned down, kept for the record

Writing a good one

The template asks for context, decision, consequences split into good and bad, and alternatives considered. Two of those sections carry the weight.

Record the real downsides. Every ADR in this repository lists what the decision made worse. A record with no bad consequences is a record that was not thought through.

Give each alternative the reason it lost. "We didn't like it" is not a reason. Every rejected alternative in these records carries the specific trade-off that decided it, which is what makes the decision reviewable years later by someone who was not in the room.

Related pages

Clone this wiki locally