October 2026 Feature Updates
October opened with a week of authentication hardening and a new self-service sign-up flow (verification codes, invitations, a bot check), then built SMART App Launch out from standalone to EHR launch: a $launch operation, launch context carried on tokens and refresh tokens, and patient/ scopes bound to one compartment. Mid-month rebuilt the server's request pipeline around typed lanes, exposed every FHIR operation as an MCP tool with a conformance suite behind it, and landed the FHIR asynchronous request pattern. The last ten days were worked largely from the new agent task board: dozens of FHIRPath functions and operators, a dozen FHIR search result parameters and prefixes, AccessPolicyV3 and DeidentificationPolicy definitions, a TestScript suite for the ONC (g)(10) test kit, and a conformance site that reports every suite.
Backend Changes
SMART App Launch: EHR launch
ClientApplication/{id}/$launchstarts an EHR launch of an app for a patient and, optionally, one of the patient's encounters. Haste Health plays the EHR: the operation saves the launch context and returns the app'slaunchUri(a newClientApplicationelement) withissandlaunchadded to its query. The launch value expires after five minutes, only a signed-in user can start one, and the same user must then authorize the app.- The authorize endpoint accepts
launchalongside thelaunchscope and resolves it before consent, so nobody is asked to approve an app for a launch that cannot be used, and anaudparameter must name the project's FHIR base URL. The token response carriespatient,encounterandneed_patient_banner, the launch context rides on the refresh token so a refreshed token stays bound to the same patient and encounter, and a user who is a patient may only launch an app for themselves. - Follow-ups from the (g)(10) work:
smart_style_urlin launch token responses, backed by asmart-style.jsonendpoint; the OpenID configuration served at the id token issuer (/w/{tenant}/{project}/.well-known/openid-configuration) with the SMART configuration'sissuermatching it; an unparseable scope redirected to the registeredredirect_uriwitherror=invalid_scopeandstateinstead of answered with a 501; andauthorize-post,permission-v1,context-banner,context-standalone-patientandcontext-styleadvertised as capabilities. - A public client may omit
client_idwhen refreshing, since the refresh token names the client it was issued to. The SMART on FHIR and client registration docs were rewritten around the flow. - PRs: #1057, #1059, #1062, #1065, #1066, #1067, #1070, #1068, #1315, #1316, #1317, #1321.
Patient-scoped access
- SMART v1
patient/scopes are issued as read and search only, bound to one Patient compartment. The patient comes from the Membership's link or from the EHR launch; a patient-bound scope with no patient in context is dropped rather than issued unbound, and the response'sscopetells the client what was issued. - A search on a compartment type runs with a filter over the R4 compartment parameters, a read outside the compartment answers exactly as one that does not exist, and writes, history, system-level search, chained and
_hasparameters are refused because none of them can be bounded. Reference data that noCompartmentDefinitioncan place (shared types) stays readable as a whole. - TestScript suites under
backend/testscripts/access-controlcover patient, user and system scopes and the SMART launch. A headless login script and an access-control workflow run them in CI, and the CLI gainedlogin --no-browserandadmin user set-passwordso a pipeline can sign in without a browser. - PRs: #1048, #1049, #1052.
Asynchronous requests
- Operation invocations accept
Prefer: respond-async: the server queues a job, answers202 Acceptedwith the job's status URL inContent-Location, and runs it in the background with exactly the permissions the request had. Every check the synchronous form makes (authentication, scopes, access policies, rate limits, that the operation exists) happens before the job is accepted. GETon the status URL answers202withX-ProgressandRetry-Afterwhile the job runs, then the response the synchronous request would have had, or the operation's own error.DELETEcancels. Results stay available for 24 hours by default, a project may have ten queued or running jobs at once, and the CORS layer exposes the headers a browser client needs.- Jobs live in a new
async_jobstable and run from the worker, which split intohaste-health worker indexingandhaste-health worker async-jobsso the two can scale on their own. - PRs: #1115, #1116, #1273, #1274, #1275, #1276.
FHIRPath
- The evaluator closed most of its gap to the specification. New functions:
all,allTrue,anyTrue,allFalse,anyFalse,subsetOf,supersetOf,distinct,isDistinct,select,single,last,tail,skip,take,union,combine,intersect,exclude,iif,not, the math functions,indexOf,substring,length,toChars,replace,matches,replaceMatches,startsWith,endsWith,contains,trace,is, everytoXandconvertsToXconversion including dates, times and quantities,now,today,timeOfDay, and$indexinside iteration functions. - New operators:
in,contains,implies,div,mod, unary+and-,&,~and!~, and ordered comparisons on strings as well as numbers. Date, DateTime, Time and Quantity literals parse, and+and-work between a date or time value and a time-valued quantity, with month ends clamped. The parser resolves string escapes (so the regexes in R4 invariants reachmatchesintact) and accepts comments. - The FHIR-specific functions (
extension,hasValue,getValue,htmlChecks,memberOf,subsumes,subsumedBy,conformsToand a realresolve) arrived behind aFHIRServicestrait; the server does not wire its terminology service into it yet. and,orandxorfollow three-valued logic for empty operands. This changes whatPatient-deceasedindexes for patients withoutdeceased[x], so existing Patient data needs a reindex.- Compiled regular expressions are cached by pattern, like the expression ASTs, and the specification's own examples run as tests in the crate.
- PRs: #1063, #1159, #1160, #1206, #1208, #1209, #1210, #1211, #1212, #1213, #1214, #1215, #1227, #1255, #1257, #1258, #1277, #1278, #1293.
FHIR search: result parameters and prefixes
_include=*andType:*expand to every reference parameter of the matched types; a system-level search resolves_includeand marks every entry'ssearch.mode;_typefilters a system-level search;_count=0returns an accurate total with no entries;_sortaccepts number, quantity and uri parameters;_summary=datadropstext, and matches trimmed by_elementsor_summarycarry theSUBSETTEDtag;_formatand_prettyare accepted on searches; and_listrestricts a search to a List's items (PostgreSQL only).- URI parameters gained
:aboveand:below. Escaped\,,\|and\$in parameter values are honored by the server and the TypeScript client, andneon a quantity negates the whole match, value and unit, on every backend. - Modifier and prefix parsing moved into shared enums used by the PostgreSQL, Elasticsearch and in-memory backends, and the in-memory backend now runs the same clause semantics as the other two.
- New TestScripts pin
:missingacross parameter types,eqon numbers with the specification's precision examples, and every quantity prefix. - PRs: #1074, #1235, #1251, #1252, #1253, #1254, #1259, #1260, #1261, #1262, #1263, #1266, #1267, #1268, #1269, #1271, #1285.
Search totals
_total=estimateand_total=accurateon PostgreSQL count exactly up to a bound (10,000 rows or 100 ms for an estimate, 50,000 rows or 500 ms for accurate) before falling back to the planner's estimate, and the page corrects the result: a short page is exact, a full page is a lower bound, an empty page past the start is an upper bound. A TestScript checks the total on every page shape.- PRs: #1108, #1260.
Operations
- A
GETon/$op,/[type]/$opor/[type]/[id]/$opruns the operation when itsaffectsStateis false, with the query string typed intoParametersfrom theOperationDefinition. A state-changing operation answers 405, and the destructive built-ins ($delete-refresh-token,$delete-scope,$launch,$tenant-customization) are now markedaffectsState = true. - The built-in operations'
OperationDefinitionresources are served beside a project's own through an in-memory search backend: a read of a built-in's id returns it, a type search lists it ahead of the project's, and a project definition that would shadow a built-in is refused. - Invocation responses come back at the level they were invoked at, so the typed client methods work for instance and type invocations, and
ValueSet/{id}/$expandand$validate-coderead the instance they target.$viewdefinition-runno longer needs_since: it walks the type's history, keeps each resource's current version, leaves deleted ones out, and the Binary it returns carries acontentType. - PRs: #1319, #1074, #1107.
Request pipeline
- The server's FHIR client was rebuilt around one
PipelineofMiddlewarelayers ending in aHandler, generic over a singleBackendtype that names the repository, search engine and terminology, withNextborrowing the rest of the chain so a hop costs no allocation. The old generic middleware chain infhir-clientwas removed. - A request is then dispatched to a typed lane (clinical, artifact, OperationDefinition, project auth, tenant auth, operation, capabilities), each a pipeline of its own. An interaction a lane does not serve answers not supported, and every variant is matched so a new one fails to compile unhandled.
- Writes refuse a body whose
resourceTypediffers from the URL and an update whose body id differs from the URL id. The CapabilityStatement is cached per (tenant, project) rather than process-wide, since it lists a project's custom operations, and batch entries run with the audit repository attached. - PRs: #1078, #1079, #1109, #1110, #1089, #1090.
Sign-up, invitations and roles
- Self-service sign-up and login run through a verification-code flow with an optional Cloudflare Turnstile check and rate limiting. A
logemail backend writes the codes to the server log for local development, andsignup.enabled = falseturns self-service tenant creation off on a deployment that creates tenants with the CLI. - A user a tenant creates for someone else is an invitation: the person proves they own the address and accepts or declines, and
emailVerified, which clients cannot set, is required to log in. - Writes to
Userenforce a role hierarchy: owners may modify any user, admins may modify admins and members, and a conditional update or delete needs an owner, so an admin cannot mint, demote or delete an owner. Tenant and project ids are lowercase letters and digits joined by single hyphens, at least three characters, so{tenant}--{project}is a valid subdomain label that splits back apart. - PRs: #1021, #1044, #1038, #1036, #1081, #1039.
Authentication hardening
- OIDC: only the
coderesponse type is advertised or accepted; a code issued to a PKCE request can only be redeemed with its verifier, andS256is computed as RFC 7636 writes it; a confidential grant is refused for a client with no stored secret; registered redirect URIs are regex-escaped before matching; an error raised before the redirect URI is validated is answered directly rather than redirected; the session id rotates on login, TOTP verification and the federated callback; a password change revokes the user's refresh tokens; and OAuth errors use theerroranderror_descriptionnames from RFC 6749. - Federated login verifies that the provider's id token is signed with an asymmetric algorithm by a key in its JWKS and names the configured client id in
aud. Haste Health's own id token took the same shape:audis the client,issis the project's issuer URL, and it carriesemail,fhirUser, tenant, project and role claims. - Timing: auth string comparisons are constant time, and login and password reset take the same time whether or not the account exists. HTML pages are sent uncompressed so a CSRF token cannot be read off response sizes, the MFA create route checks CSRF, and the TOTP redirect stays local.
- Smaller changes: Basic auth's cached tokens live five minutes;
$evaluate-policylets only owners and admins evaluate as another user; login, expired and security issue types map to 401 and 403 instead of 500; PostgreSQL error detail stays in the log rather than the OperationOutcome; scope approval reads the client application's stored id and scope; and tenant logo uploads have a size limit. - PRs: #1024, #1026, #1027, #1030, #1041, #1042, #1043, #1045, #1047, #1053, #1054, #1055, #1056, #1064, #1082, #1083, #1084, #1085, #1086, #1087, #1088, #1091, #1093, #1094.
New resource definitions
AccessPolicyV3andAccessPolicyV3Assignmentlanded as StructureDefinitions, value sets and search parameters, with a validation lane that checks rule shapes: a constraint rule needs data and no condition, a decision rule carries no data, andfieldandobligationappear only on permit rules. Evaluation stays with the V2 engine for now.DeidentificationPolicydescribes how$deidentifywill treat elements. Each rule targets apath,typeorextension, checked against the StructureDefinitions; the parameters for each action are validated; and amergelets a policy build on a parent, with the child's rules overriding by target.- PRs: #1283, #1284.
Resource author
- The author extension on
metais a canonical (https://haste.health/fhir/Membership/<id>) rather than a Reference, since an author can be a Haste Health type that other FHIR systems reject as a reference target. The server overwrites any author a client sends, and_haste-health-authorfinds a resource by it. - PRs: #1072.
HL7v2 parser
- A malformed message is rejected as
invalid(HTTP 400) instead of indexing past the end of its bytes,MSH-2is kept as one value rather than split on the encoding characters it defines, and the empty-value marker is set only on fields that are empty. - PRs: #1040, #1112, #1113.
Document storage
- A new
document-storagecrate defines aDocumentStoragetrait for document bodies (Binary content, attachments, exports) with a Google Cloud Storage provider behind a feature flag. Nothing in the server uses it yet. - PRs: #1077.
Product and UX
MCP: operations as tools
- Every invocable operation is an MCP tool named
fhir_op_<code>, discovered fromOperationDefinitionresources, so a project's own Deno-backed operations show up intools/listwith a typed schema and no registration. Tools carry annotations (read-only, destructive, idempotent) and human titles, and the FHIR version comes from the token. - The server sends
instructionson initialize: the order to work in, FHIR search syntax, and when to confirm with the user. Search parameter descriptions include each type's value syntax, an example and the modifiers it accepts, and a mistyped resource type gets the closest supported one as a suggestion. - A tool that runs and fails returns a result with
isErrorand every OperationOutcome issue on its own line with its element path, so a model can correct the call. Unknown tools and server faults stay JSON-RPC protocol errors, authentication and rate-limit failures stay HTTP statuses, and Bundle schemas no longer requireentry. - A specification-based conformance suite in
frontend/packages/mcp-conformanceruns the protocol and every listed tool against a live server in CI, with the known gaps in a continue-on-error step. - PRs: #996, #1096, #1097, #1098, #1099, #1100, #1101, #1102, #1103, #1104, #1105, #1106, #1111, #1114.
Admin console
- Operation invocation panels in the components package build an input form from an
OperationDefinition's parameters at the system, type or instance level, run it, and show the output parameters. The console and the resource editor both use them. - A launch button on Patient and Encounter views starts a SMART EHR launch of any
ClientApplicationwith alaunchUri, opening the app in a new tab signed in as the console user. The ViewDefinition view's state handling was fixed, and console routes refuse resource types and ids that are not valid before joining them into a URL. - The admin app is deployed to Cloudflare Workers by a hand-started release workflow.
- PRs: #1058, #1069, #1076, #1095, #1080.
Website
- The homepage was restyled and then laid out as a product grid: nine feature tiles, an agent session walkthrough, a conformance grid, and a facts plugin that pulls resource-type, search-parameter, check and MCP-tool counts from the generated reports at build time. The contact and pricing pages followed the new system, and the social card is generated by a script.
- Conformance moved from one core page to a page per suite (FHIR R4 core, US Core, ONC (g)(10), SMART access control) plus an overview. A
test-suitesgenerate step turns each suite's TestReports into static JSON, and the homepage's automated-checks figure is the total across suites. - PRs: #1019, #1037, #1117, #1295.
TypeScript client
- The HTTP client refuses a request whose URL resolves outside the FHIR base, so a resource type or id taken from a browser route cannot send the bearer token to another origin or path. Query parsing honors escaped separators.
- PRs: #1095, #1263.
Platform and Runtime
Indexing watermark and sequence index
max_safe_seqmakes one pass overpg_locksgrouped by pid instead of a self-join that rescanned the lock table once per registrant: 20 to 170 ms per call became 0.8 ms under the same load. The registration marker lock now blocks instead of silently dropping a writer out of the watermark, and the indexing loop's idle sleep is configurable (poll_interval_ms).- The resources sequence index leads with (tenant, project, sequence). A poll for a caught-up project whose sibling in the same tenant was a million rows ahead read about 805k buffers for zero rows; it now reads six.
- PRs: #1018, #1023.
Search backend fixes
_countand_offsetare capped at what the backends accept, and wildcard characters in string searches are escaped on both Elasticsearch and PostgreSQL (the PostgreSQL fix came from an outside contributor, below), with a TestScript pinning the behaviour.- PRs: #1025, #1046, #1264.
Validation performance
- Profile validation looks up each slice's discriminator definition once, on first use, and reuses it for the other values instead of walking the StructureDefinition per value. A benchmark and slicing tests came with it.
- PRs: #1288.
Custom operation runtime
- The Deno pool worker enters its tokio runtime for the whole loop, so warm isolates built between jobs no longer abort the process when V8 posts a delayed task.
- PRs: #1092.
Testing
- Property tests (
proptest) cover the pure parsing and range code in six crates, with FHIR primitive generators behind atestingfeature. They found and fixed four bugs: short years were not padded, impossible days such as2023-02-30now carry into the next month instead of failing, number range bounds lost their rounding tolerance above about seven significant digits, and a search number written with an exponent (1e2) got the implicit range of a plain integer. - The TestScript runner gained HTTP-level assertions (
responseCode,headerField,contentType), plain HTTP requests for URLs that are not FHIR interactions, and a poll-while extension that repeats a request while its status matches and honorsRetry-After. An async-requests TestScript uses all three. backend/testscripts/g10holds 441 TestScripts for the checks of the ONC (g)(10) Standardized API test kit that a TestScript can express: SMART standalone, limited and EHR launch, the US Core 7 Single Patient API, Bulk Data, and v1 and fine-grained scopes. Aknown-failures.txtlists what does not pass yet, and a dedicated workflow runs the suite on the PostgreSQL backend.- The e2e workflows share one build of the server across every suite, so no suite runs a binary built with different compiler settings.
- PRs: #1118, #1282, #1294, #1289.
Artifacts, Tooling, and Maintenance
Releases and distribution
- Every release sets one version across the Cargo workspace and the pnpm packages, and the login pages show it as a watermark. Linux binaries are static musl builds for x86_64 and arm64, with OpenSSL gone from the dependency tree, alongside Apple Silicon.
- The CLI is on npm as
haste-health: a wrapper package picks the binary for the platform. The README and CLI docs cover the install. - PRs: #1031, #1032, #1033, #1034, #1035.
Agent task board
AGENTS.md, imported byCLAUDE.md, carries the protocol for coding agents working the issue board: onlyready-for-agentissues are eligible, one PR per issue againstmain, proposals are filed asagent-proposed, and anything security-sensitive goes to the private repository. New issue templates require machine-checkable acceptance criteria, and labeling an issueready-for-agentfires a workflow that dispatches it. Most of the FHIRPath and search PRs above came through it.
Dependency audits
- An audit autofix workflow opens a PR when the cargo, frontend or Docker image audit fails on
main, and leaves that audit alone until the PR is merged or closed; seven such PRs merged this month (quinn-proto, rustls, ctutils, handlebars, storybook, body-parser, multer and undici). The cargo audit scans the lockfile and its ignores moved to.cargo/audit.toml, andcrypto-bigintis ignored until jsonwebtoken moves to elliptic-curve 0.14. - PRs: #1060, #1061, #1071, #1073, #1075, #1265, #1270, #1280, #1029, #1162, #1290.
Dependency upgrades
- sqlx moved to a git pin of upstream
mainfor a dangling-transaction fix. The unusedwal-workercrate was removed, and backend and frontend dependencies were brought current across several Dependabot rounds. - PRs: #1015, #1016, #1020, #1050, #1051, #1204, #1291.
CI hygiene
- GitHub Actions are pinned to commit SHAs, the Rust toolchain is pinned for clippy so a new release cannot fail PRs, and two rounds of Sonar fixes went in across the frontend packages and the server.
- PRs: #1028, #1017, #1022.
Docs
- The SMART on FHIR, client registration, federated login and refresh grant pages were rewritten for the launch work, asynchronous requests and the
[signup]configuration got pages of their own, and the OAuth error and MCP endpoint docs were corrected. - PRs: #1068, #1064, #1081, #1276, #1035.
Standalone Commits (No PR Link)
- Agent task board:
AGENTS.mdand theCLAUDE.mdimport, the issue templates, and theready-for-agentpickup workflow. - CI: workflows renamed into
test_*,release_*andaudit_*groups, Postgres pulled frommirror.gcr.io, and audit autofix adjustments. - Tests: a TestScript for
LIKEwildcards in string searches, plus clippy and borrow fixes in the search crates and the HL7v2 parser. - CLI: the
fhirpathandapicommands read a multi-line resource from stdin. - npm: publish config for the
haste-healthpackage, and the TypeScript CLI made private. - Small backend follow-ups: the server client's remaining unimplemented typed methods filled in, and a formatting pass.
- Version bumps: automated since #1031, from
841f36ef4(0.126.3) to6193673d7(0.126.38).
Contributors
Thanks to masyarakatai for escaping %, _ and backslash in PostgreSQL string searches (#1264): the generated LIKE predicates now carry an explicit ESCAPE clause, exact-match values are left as they are, and unit tests cover both.
