The fhir-rust monorepo specification
This directory is the root of every normative specification in the repository. It does not itself state requirements. It says which specification governs which code, how their requirement numbers relate, and where a citation resolves — so that a number written in a commit message, a test name, or an auditor's workpaper has exactly one meaning.
Operational guidance for contributors lives in AGENTS.md and
AGENTS/. Those say how to work; the specifications
below say what must be true. When they conflict, the specification wins.
The four families#
The repository holds four bodies of code. Three were specified independently and still are; the persistence core is governed by the database specification rather than one of its own, because it is the ports' shared half. They share a domain, a language, and a discipline; they do not share requirement numbers, release cadence, or a conformance model.
| Family | Code | Specification | Ids | Status |
|---|---|---|---|---|
| Model | fhir/ |
fhir/spec/index.md — 14 sections |
R1.x–R14.x |
specified, versioned 3.0.0 |
| Persistence core | fhir-store/ |
governed by spec/databases/ — M3.16, PR12.x |
shares the database ids | specified |
| Databases | fhir-postgresql/ and five sibling ports |
spec/databases/index.md — sections 0–16 |
see table below | specified, pre-release |
| HTTP surface | fhir-loco/ |
fhir-loco/spec/index.md — 4 sections |
SV1.x–SV4.x |
specified 2026-08-03 |
They compose in one direction only:
fhir/ FHIR R2–R6 as Rust types, generated from the spec packages
│ (no database, no I/O)
│
fhir-store/ the engine-agnostic half of persistence: audit chain,
│ attribution, result types (no driver, no socket)
▼
fhir-<engine>/ those resources shredded into real relational tables
│ (no HTTP, no CLI — C0.17, C0.18)
▼
fhir-loco/ an HTTP surface over one of those stores
Nothing lower in that stack may depend on something higher. A requirement about JSON fidelity belongs to the model family even though the database family also depends on it; the database family cites it rather than restating it.
Precedence#
- Within a family, that family's specification is normative for its code.
- A port dialect annex (
M14.x, one per database port) is normative only where it explicitly amends a core requirement by number.M14.6 amends M3.4is a departure; prose that merely differs is not. - Where a port has no annex text on a subject, the database core governs unmodified.
- No family is normative for another. A
fhir/specrequirement does not bind a database port, and a database requirement does not bind the model crate. Where one family relies on another's guarantee, it cites it as an assumption, not as a rule it enforces. - Nothing in a
README.md,book/chapter,plan.md,tasks.md, or code comment is normative. Those describe; these decide.
An undeclared departure is a defect in the code, not an amendment to the specification.
Requirement-id namespaces#
Every id is <prefix><section>.<n>, optionally with a letter suffix for a
requirement split after the fact. Ids are permanent (C0.5): never
renumbered, never reused, including across a file move.
Databases — spec/databases/#
| Prefix | Section | Subject |
|---|---|---|
C0 |
0 | normative language, id grammar, conformance levels |
S1 |
1 | FHIR versions, resource coverage, engine floors |
G2 |
2 | determinism, identifiers, install |
M3 |
3 | tables, types, extensions, audit, hash chain |
R4 |
4 | lossless round-trip, snapshot reads |
H5 |
5 | versions, soft delete, vread |
P6 |
6 | parameters, folding, indexes, bounded cost |
V9 |
9 | structural, strict, terminology gap |
O10 |
10 | logging, migrations, TLS, supply chain |
T11 |
11 | what must be tested, and how honestly |
PR12 |
12 | attribution, disclosure logging |
M14 |
14 — per port | that engine's declared departures |
X15 |
15 | what is shared, what an annex must say |
W16 |
16 | layout, SSOT, versioning, release |
L1–L16 |
locale and accent folding | the fold, normatively |
U1–U13 (incl. U1a, U2a, U2b, U4a, U11a) |
search adjuncts | bounded and checksum adjuncts for any search-reachable column a dialect cannot index or compare — string, CLOB, BLOB, fixed-shape |
Sections 7 (REST API) and 8 (CLI) are retired — the ports are embeddable
libraries. The numbering keeps the gap rather than renumbering, so V9.2 still
means what it meant. Section 13 is a compliance mapping table and defines no
requirements of its own.
HTTP surface — fhir-loco/spec/#
| Prefix | Section | Subject |
|---|---|---|
SV1 |
1 | what the crate is, three conformance levels, honesty |
SV2 |
2 | routes, status codes, OperationOutcome, CapabilityStatement |
SV3 |
3 | PASETO, the principal, what the store is told |
SV4 |
4 | limits, configuration, binding, logging |
SV collides with neither other family, deliberately — see the R4 note below
for what a shared prefix costs once ids are permanent.
It restates rather than moves the requirements that already described this
crate: §7 and §8's retired ids (A7.8, A7.10–A7.12, M8, registered in
C0.16) and §10/§12's [service]-marked ones. Moving them would renumber
across families, which C0.5 forbids and which is how the R4 collision
happened.
Model — fhir/spec/#
Numbered R<section>.<n> throughout, one prefix per section: R2 primitive
types, R3 complex datatypes, R4 resources, R5 code systems, R6
serialization, R7 validation, R8 code generation, R9 primitive
extensions, R10 invariant coverage, R11 choice types, R12 FHIR releases,
R13 assurance, R14 cross-release conversion. Section 1 is an overview and
defines no requirements.
The R4 collision — read this before citing R4.x#
R4.1–R4.7 exist in both families and mean unrelated things:
| Id | fhir/spec/04-resources.md |
spec/databases/04-shredding-and-reconstruction.md |
|---|---|---|
R4.1 |
each resource MUST be a Rust struct with the canonical derives | shred and reconstruct MUST be driven by the generated map through one generic engine |
R4.2 |
fields MUST use the cardinality mapping of spec 06 | round-trip MUST be lossless, decimal precision and partial dates included |
This is the only overlap between the two families — every other database
section carries a distinct letter (S1, G2, M3, H5, P6, V9, O10,
T11, PR12, X15, W16), so R4 is the single ambiguous prefix.
A bare R4.x is therefore not a citation. Qualify it:
db:R4.2orspec/databases R4.2— the round-trip requirementmodel:R4.2orfhir/spec R4.2— the cardinality requirement
Existing bare citations are resolved by the file they appear in: a citation
inside fhir/ means the model spec, one inside fhir-<engine>/ or
spec/databases/ means the database spec. Neither family may renumber to
remove the clash (C0.5), so the qualifier is the fix.
Status, not requirements#
These record reality rather than intent, and are non-normative:
| databases conformance matrix | which port satisfies which core requirement today |
| databases audit | every known divergence between spec, docs, and code, with evidence |
fhir/spec/13-assurance.md |
what must hold before the model crate is depended on clinically |
| publishing readiness | what stands between all 33 crates and crates.io — cross-family |
The conformance matrix is the document to trust for port status. A README, a
book/ chapter, and a tasks.md checkbox are all weaker evidence than it, and
in this repository all three have been wrong.
Gaps#
Recorded here so that "no specification" is a stated fact rather than an oversight a reader has to infer:
Closed 2026-08-03. It now has one:fhir-loco/has no specification.fhir-loco/spec/, idsSV1.x–SV4.x. Every promise this gap listed —410versus404,If-Match,_count/_offset/_total, the CapabilityStatement — is now a numbered requirement that can be cited and shown to have regressed.Sections 7 and 8 of the database spec stay retired: HTTP and CLI are out of scope for the ports, which was always the right reading. The new specification restates their obligations under
SVids rather than moving them, becauseC0.5makes ids permanent.Resolved the same day. The owner chose the second of the two ways out:
fhir-locohas its own specification, and it restates the[service]obligations underSVids while citing the originals. The[service]markers in §10 and §12 stay and now mean "bindsfhir-loco, restated asSV4.x".What it does not meet is recorded at the
SVid rather than in a list that drifts:SV4.2(no concurrency or in-flight limit — Loco 1.0.1 exposes neither),SV4.3(no admin plane, no/metrics),SV2.14(no conditional create),SV2.15(no$export), andSV3.11(no requirement anywhere states an obligation for the listener's own TLS).All six dialect annexes are still marked proposed (
X15.9), so none may be cited as evidence for a conformance level.
Where to start#
| You are | Read |
|---|---|
| evaluating the databases | README.md, then the conformance matrix |
| implementing a database port | spec/databases/index.md, then §15 |
| changing the model crate | fhir/spec/index.md |
| auditing compliance | §13 compliance mapping |
| releasing to crates.io | publishing readiness first, then §16 W16.x |
| contributing at all | AGENTS.md |