FHIR conditional write plans¶
openmed.interop.fhir.conditional_writes builds local, typed FHIR R4 conditional-create and conditional-update plans. It makes no network request, holds no resource payload, and never executes a write. The caller supplies a private deployment secret and explicit matching criteria.
from openmed.interop.fhir.conditional_writes import (
ConditionalWriteKind,
assess_matches,
build_conditional_write_plan,
)
from uuid import UUID
# The value and key below are synthetic. Load the real key privately at runtime.
secret = b"synthetic-deployment-secret-32-bytes-long"
plan = build_conditional_write_plan(
ConditionalWriteKind.CREATE,
"Observation",
{"identifier": "urn:synthetic|example"},
operation_id=UUID("00000000-0000-4000-8000-000000000001"),
secret=secret,
)
decision = assess_matches(plan, 0, search_complete=True)
decision.require_ready()
The predicate is sorted by search-parameter name and encoded with RFC 3986 percent encoding. plan.predicate.canonical_query is sensitive request content: use it only in the authorized request path or If-None-Exist header. Never put it in logs, exceptions, traces, or reports. The plan and predicate redact it in their representations. A single value per parameter is supported; repeated parameters and comma-separated OR values are rejected rather than assigning server-dependent match semantics.
The stable fhir-cw-v1- key is an HMAC-SHA256 commitment to an opaque operation UUID, interaction, resource type, and canonical predicate, separated by a versioned domain. Persist the same UUID and private secret across retries of one operation. Use a new UUID for a distinct intended write, even when its predicate is unchanged. Rotate the secret only when those retries have ended. The key is planning metadata. A server may not support an idempotency header, so the key by itself never guarantees a duplicate-free write. The caller must use FHIR conditional request semantics and still handle concurrent changes or server conflicts.
Before any request, check the already-cached server CapabilityStatement with preflight_write_plan(), setting conditional=True for the same create or update interaction. Search for the plan's predicate and evaluate a complete result set with assess_matches():
| Interaction | Matches | Decision |
|---|---|---|
| Create | 0 | Ready for explicit conditional create |
| Create | 1 | No-op: existing match |
| Update | 1 | Ready for explicit conditional update |
| Update | 0 | Review: a server may create on conditional update |
| Either | More than 1 | Review: ambiguous matches |
| Either | Incomplete search | Review: match count is unreliable |
require_ready() raises a value-free exception for every no-op or review decision. A reviewer must resolve ambiguous matches; the planner never picks a resource. Search results can change after this check. An executor must also stop on server-side ambiguity and conflicts, and must satisfy its own approval, concurrency, provenance, and recovery controls before any write. These controls are tracked separately by issues #2774, #2777, #3199, and #3197.