Skip to main content
fi-fhir docs

Supported 1.0 Baseline

This document fixes the target environment for fi-fhir 1.0 evidence. It is a test contract, not a claim that the current pre-1.0 repository has completed every release, conformance, performance, recovery, or compatibility gate.

Status vocabulary

  • locked: the repository and required CI already enforce this choice;
  • reference target: future 1.0 proof must use this exact profile unless a dated architecture decision changes it;
  • release gate: still requires archived end-to-end evidence before 1.0.

Platform matrix

Area1.0 baselineCurrent status
Security domainOne logical healthcare-organization security domain per deployment; tenant and actor identity remain mandatory on runtime and durable contractsReference target; shared multi-tenant hosting is not a 1.0 claim
Backend toolchainGo 1.26.5Locked by go.mod, CI, and the production image build
UI and SDK toolchainNode.js 22 and npm 10.9.3Locked by CI and frozen npm lockfiles
Runtime OS/architectureLinux amd64 reference; Linux arm64 release targetamd64 is the reference target; arm64 needs release-gate runtime evidence
PersistencePostgreSQL 16Locked for Compose and CI integration services; production recovery evidence remains a release gate
Local deploymentDocker Compose with PostgreSQL 16Development reference; not a production topology
Kubernetes deploymentKubernetes 1.36.x through Helm and KustomizePinned reference target; render, install, upgrade, rollback, and live golden-journey evidence remain release gates
Authoring UICurrent SvelteKit build served as static assetsBuild/test locked; latest-two Chrome, Edge, and Firefox plus current Safari compatibility remains a release gate
Healthcare standardsFHIR R4 4.0.1, US Core 9.0.0, SMART App Launch 2.2.0, Bulk Data 3.0.0Release targets; official validator or conformance-suite evidence is not yet complete. Nothing in the repository is pinned to a US Core package — see "FHIR profile-version assertion policy" below

Kubernetes 1.36 is the pinned minor because it is an actively supported upstream release during the Engine Alpha program. Patch releases may advance within 1.36 for security and defect fixes; changing the minor requires a dated decision and a full deployment proof rerun.

FHIR profile-version assertion policy

Decided 2026-08-09, Slice 5.1a. Recorded in .loom/40-decisions.md and docs/planning/FHIR-CONFORMANCE-MATRIX.md; asserted by TestFHIRConformance_ProfileVersionPolicy.

The mapper asserts bare canonicals. The checker accepts a bare canonical or any |version-pinned form of it.

Concretely, a resource this product emits declares http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient, never …/us-core-patient|9.0.0. A resource this product validates may declare either.

Two consequences worth stating plainly, because the row above lists US Core 9.0.0 and a reader could reasonably infer more than is true:

  • The product is not pinned to US Core 9.0.0. None of the 32 US Core profile constants carries a version suffix, no IG package is vendored, and nothing resolves a profile against a package. 9.0.0 is a release target. Do not describe the repository as "US Core 9.0.0-pinned".
  • The shipped checker is a required-element and profile-URL presence check, not a profile validator. It has no terminology bindings, no primitive-type checks, no slicing, and no invariants. Version tolerance is not version resolution: stripping a |version suffix lets a correctly pinned resource pass the presence check; it does not verify that the resource conforms to that version of that profile.

The alternative — pinning all constants to |9.0.0 and requiring an exact match — was rejected. Without a package-resolution step a pinned constant asserts a version it cannot verify, and it would reject a correct bare canonical. Version resolution arrives with the pinned offline .tgz packages in Slice 5.1b, and item 7 of the evidence list below stays blocking until then.

Reference application profile

The performance and recovery gates use this application-side reference profile:

  • Linux amd64;
  • 4 vCPU and 8 GiB RAM per application replica — not across the deployment. Kubernetes resources are per container, so a total figure cannot be expressed in the values file that carries the profile, and halving the envelope to make the prose match would be a capacity change made on no measurement. Two replicas therefore total 8 vCPU / 16 GiB;
  • two application replicas for Kubernetes scenarios;
  • PostgreSQL 16 on SSD-backed persistent storage;
  • destinations decoupled from durable acceptance for latency measurements;
  • 2-KiB HL7v2 messages for the baseline throughput journey.

The chart defaults are not the reference profile

deploy/helm/fi-fhir/values.yaml requests 100m CPU and 128Mi memory and limits each pod to 500m CPU and 512Mi memory. A 4 vCPU / 8 GiB budget cannot be met inside a 500m limit, so the chart defaults and the profile above are not two views of one thing — they are a scheduling default and a measurement target that happen to live in the same repository.

Slice 4.4a resolves the contradiction by naming it rather than by moving a number. Raising the chart defaults to the profile would change what every existing deployment schedules, on the strength of a profile nothing has yet measured against — a capacity claim dressed as a chart edit.

The resolution:

  • The chart defaults stay. They are what a small or evaluation deployment should schedule, and they are labelled as scheduling defaults in values.yaml itself.
  • deploy/helm/fi-fhir/values-reference-profile.yaml carries the profile. It sets requests and limits to the 4 vCPU / 8 GiB envelope and two replicas, and is the file any performance run must use: helm install ... -f deploy/helm/fi-fhir/values-reference-profile.yaml.
  • Slice 4.4b built the harness and made the runner decision (see the budget table below). The decision was not "wait": CI's k3s pool spans hardware differing by more than 5×, so a latency budget measured there is either permanently red or calibrated into meaninglessness — but allocation counts do not depend on the machine, so the durable accept path is now gated on those, blocking, in the ordinary pool. Wall-clock and throughput are measured by a job that only runs on a pinned runner, which does not exist yet.

Until that runner exists and archives a report, no document may describe the chart defaults, or the reference profile, as proven capacity. The profile is the environment a future measurement must use; it is not a claim that the software performs at that scale.

Required evidence before a 1.0 support claim

The following remain blocking:

  1. Golden Path 001 production/preview parity, durable receipt, duplicate, restart, and profile-revision tests;
  2. authenticated HTTP and MLLP protocol journeys;
  3. Helm and Kustomize render plus Kubernetes 1.36 install, upgrade, rollback, and uninstall proof — render and schema validation closed by slice 4.4c (scripts/validate-k8s-schema.sh, blocking in lint:helm): the chart at default values, the chart at the reference profile, the Kustomize base, and the production overlay all validate -strict against the pinned 1.36 API schemas, with a negative control that must be rejected. Both application Deployments now declare a rolling-update budget, a grace period, and a preStop, and the PostgreSQL Deployment uses Recreate so it cannot wedge on its ReadWriteOnce volume. Live install, upgrade, rollback, and uninstall evidence on a 1.36 cluster remains blocking and is the last RC item;
  4. PostgreSQL backup/restore and the documented RTO proof — closed by slice 4.4c: test:migration-compatibility proves the restore is faithful (rows, PHI payloads, immutability guards attributable to those guards by SQLSTATE, the NOT VALID provenance CHECK, and all six schema ledgers at their declared versions), proves the delivery worker resumes from the restored state, and archives a measured recovery time as recovery-rto.json. The RPO half stays open and is an operator responsibility with a stated method, not a product claim: bounding data loss to minutes requires continuous WAL archiving and point-in-time recovery, which belongs to whoever runs the database. See docs/operations/PRODUCTION-HARDENING.md, "What this repository claims, and what it hands to the operator", and .loom/40-decisions.md (2026-08-09, "WAL/PITR posture");
  5. reference-profile latency, throughput, soak, and recovery reports (budget by budget, below);
  6. browser/accessibility matrix evidence;
  7. official healthcare standards conformance evidence where applicable;
  8. security, PHI, secret, tenant-isolation, and audit kill-tests.

Performance and recovery budgets, one by one

The word "harnessed" below is doing real work. It means the path is measured and a regression in it fails a blocking CI job — and it does not mean the numeric budget has been demonstrated. Those are different claims and this table keeps them apart on purpose.

#BudgetStatusWhat exists, and what is missing
1Authenticated MLLP and HTTP durable-accept latency (p95 ≤ 250 ms, p99 ≤ 500 ms)Harnessed, uncertifiedinternal/integration/perf benchmarks both paths against a real PostgreSQL, and bench-check -set=durable gates their allocs/op with allow_failure: false. Wall-clock is measured but not asserted anywhere: a millisecond ceiling calibrated for a pool spanning 5.3×, in a 1-CPU pod sharing space with a database container, is not evidence. Certification needs a pinned runner.
2One-hour steady-state throughput on the reference profileHarnessed, uncertified, and additionally blockedSame harness, same gate. Blocked twice over: on the pinned runner, and on slice 4.4e. Until the MLLP token bucket is per deployment rather than per replica, a 250 msg/s run on two replicas against a revision declaring 250 msg/s admits up to 500 and measures the deployment topology instead of the declared policy.
31-GiB batch import peak memory above idleHarnessed, uncertifiedperf.HeapSampler measures peak heap above an idle baseline; runtime.ReadMemStats appeared nowhere in first-party code before it. It reports HeapAlloc, not RSS: a Go process's RSS includes heap the collector has freed and not returned to the OS, so RSS is a property of GC timing as much as of the workload. A true RSS figure has to come from the pinned-runner job reading the cgroup.
4Recovery time objectiveNot started — slice 4.4c4.4a proved a pg_dump/restore round-trip preserves every durable row and trigger. It measured no recovery time.
5Recovery point objectiveKnown unachievable as configured — slice 4.4cPRODUCTION-HARDENING.md states it directly: logical dumps cannot meet a minutes-scale RPO, and nothing in this repository configures WAL archiving or PITR.
6One-version rollback safetyCertified — slice 4.4aN-1 defined per migration ledger, a real defect found and fixed (0004_export_attribution.sql made three columns NOT NULL with no DEFAULT, so rollback failed every session export), and a restore round-trip proof in CI.
7Golden-journey evidence on Kubernetes 1.36Not started — slice 4.4cNeeds a cluster.

Budgets 1, 2 and 3 are gated on allocations only, and that gate is narrower than it sounds. Measured over three runs, the durable accept path's allocation count varies by about 2 in 4650 — stable, but not the bit-identical figure the legacy micro-benchmarks report. A ceiling roughly 1% above the observed count detects a regression of some 40 allocations per message. It will not notice one. It is a regression detector for the thing that causes latency, not a measurement of latency.

What unblocks certification. A GitLab runner registered in platform/gitops carrying the tag fi-fhir-perf, with at least 4 CPU and 8 GiB available to the job. The repository side is already written and inert: the job exists, carries the tag, is when: manual and allow_failure: true, and stays invisible in a normal pipeline until the runner is there. It archives performance-report.json, whose certified field may only be set true by a run that happened on that tag. Nothing else in this repository is waiting on anything.

Until those gates pass, documentation must describe individual capabilities and their evidence rather than label the whole product “1.0 certified,” “HIPAA compliant,” or standards conformant.

Supported 1.0 Baseline | fi-fhir docs