feat: strict spec-conformance mode, $validate operation, and search/storage correctness fixes - #60
Merged
Merged
Conversation
ProcessTypedValues now short-circuits when Modifier is :missing, so boolean `true`/`false` values are never fed into typed parsers. Both TryParseDateString (instance, used for search-parameter values) and TryParseFhirDate (static, used for stored resource values) now guard the 4-char year branch with int.TryParse to avoid throwing on non-numeric input. Adds regression rows to PatientSearch in R4, R4B, and R5 covering `birthdate:missing=true` and `birthdate:missing=false`. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Rewrites the prefix branches in EvalDateSearch.TestDate to follow FHIR R4 search interval semantics (https://www.hl7.org/fhir/R4/search.html#prefix): - ne: target NOT wholly inside the search interval (was: endpoints must differ — wrong) - lt: target starts before reqStart (was: valueEnd < reqEnd) - ge: target intersects [reqStart, +inf) (was: valueEnd >= reqEnd — caused instants at the boundary to be excluded) - le: target intersects (-inf, reqEnd] (was: valueEnd <= reqEnd) Also fixes a pre-existing crash in TryParseFhirDate / TryParseDateString when parsing dateTime strings with an explicit offset (e.g. '2017-05-03T15:54:26-04:00'): DateTime.TryParse(.., RoundtripKind) returns DateTimeKind.Local, and 'new DateTimeOffset(dt, TimeSpan.Zero)' threw because the local offset did not match TimeSpan.Zero. Now normalize to UTC via ToUniversalTime() for Local-kind values. Adds boundary InlineData rows to PatientSearch (R4, R4B, R5) covering ge/gt/lt/le/ne against birthdates that touch year boundaries, and date prefix rows to ObservationSearch (R4, R4B, R5) against effectiveDateTime. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replace the placeholder 'within 1 day of endpoint' heuristic in EvalDateSearch with a spec-flexible, precision-aware fixed window. TryParseDateString now emits an approxDelta alongside (start, end); the size is derived from the granularity of the search string: - YYYY -> +/- 1 year - YYYY-MM -> +/- 2 months - YYYY-MM-DD -> +/- 1 month - date+time -> +/- 1 day EvalDateSearch.TestDate (Approximately) widens the search interval by the delta and tests for overlap with the target interval. The deltas live in a new ParsedSearchParameter.ValueDateApproxDeltas array populated next to ValueDateStarts / ValueDateEnds. Adds ap regression rows to PatientSearch and ObservationSearch (R4, R4B, R5) covering same-year, adjacent-year, far-future, and month/day granularity windows. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Default string searches (StartsWith and Contains, including HumanName and Address variants) now NFD-normalize both sides and strip combining marks before OrdinalIgnoreCase comparison, per FHIR R4 section 3.1.1.3 (default is case- AND accent-insensitive). :exact searches are left untouched (they are accent-sensitive per spec). Also fixes a pre-existing shadowing bug in TestStringStartsWithAgainstAddress / ContainsAgainstAddress: the nodeVal.Line lambda parameter was named 'v', shadowing the outer search value 'v' and effectively comparing the line to itself. Renamed to 'ln'. Adds a second name with family='Muñoz' to pat1 in R4, R4B, R5 fixtures (no count changes) and family=munoz / MUÑOZ / :contains / :exact regression rows to PatientSearch in each version. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Per FHIR R4 spec (https://www.hl7.org/fhir/R4/search.html#modifiers), the :not modifier matches resources where the search parameter is not present, in addition to resources where it is present with a value other than the supplied one. The previous behavior returned false for the empty-extracted path, excluding such resources. Adds PatientSearch regression rows for gender:not=male and gender:not=female in R4, R4B, R5; R4 includes a no-gender patient (cdex bundle) and exercises the new branch directly. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
ParsedSearchParameter.Parse already uses query.GetValues(key) to produce one ParsedSearchParameter per repeated occurrence, which SearchTester.TestForMatch then ANDs. The bug report flagged this as suspicious, but no code change is needed. Adds regression rows for 'birthdate=ge1980&birthdate=le1990' in R4, R4B, R5 PatientSearch to pin the AND behavior on the date-prefix path that Phase 2 / Phase 3 reworked. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… match Per FHIR R4 REST spec section 2.1.0.10, POST /Type MUST always assign a server-side id. The previous code preserved client-supplied ids whenever _config.AllowExistingId was true (the default), which is spec-noncompliant for POST. DoInstanceCreate now ignores _config.AllowExistingId for callers whose Interaction is TypeCreate or TypeCreateConditional; the legacy permissive flag is preserved only for non-POST create paths (load-from-disk update-as-create) and for callers that explicitly pass forceExistingId=true (bundle ingest, transaction processing). DoInstanceUpdate adds an id-mismatch guard: when both the URL id (ctx.Id) and body id (content.Id) are present and differ, the request is rejected with HTTP 422 Unprocessable Entity. Storage-layer tests that intentionally exercise the legacy 'POST preserves client id' path now pass forceAllowExistingId: true explicitly. FHIR R5 subscription update tests are updated to keep the Encounter id intact across the in-progress → completed transition (the Replace pattern was clobbering both id and status). Adds cross-version TestPostPutIdSemantics in FhirStoreTests covering POST id discard (with/without body id), PUT id-match accept, and PUT id-mismatch reject (422). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Adds OpValidate to FhirStore.CommonVersioned/Operations. Auto-discovered via VersionedFhirStore.CheckLoadedOperations and registered for R4, R4B, and R5 (one shared source, three version-specific assemblies via the shared-project mechanism). The validator delegates to Firely's built-in POCO attribute validator (PocoValidationExtensions.Validate) using a ModelInspector bound to the version-specific Firely model assembly. Issues are mapped into OperationOutcome.IssueComponent entries with Severity, Code, Diagnostics, and Expression (from InstancePath / MemberName) so that matchers like fhir262's toHaveIssueWithExpression can read them. Supports all three invocation shapes: POST /<tenant>/<Type>/\ (type-level, body=resource) POST /<tenant>/<Type>/<id>/\ (instance-level, focus resource) POST /<tenant>/\ (system-level) POST /<tenant>/Parameters/\ (system-level via wire-type wrapper) Always returns HTTP 200 + an OperationOutcome (with one Information 'All OK' issue when there are no problems) — validation failures are conveyed via issues, not status codes, per FHIR convention. Controller wiring (PostTypeOperation / GetTypeOperation): when the URL resource is the wire-type 'Parameters', skip the SupportsResource check and dispatch via store.SystemOperation instead of TypeOperation (the latter does _store.TryGetValue and 404s on 'Parameters'). This is a v1 structural validator: covers required cardinality, primitive types, regex constraints, and FhirXhtml narrative. Profile / binding / slicing validation is out of scope and tracked as a follow-up in scratch/0601-01/plan.md. Cross-version smoke tests added in FhirStoreTests.TestValidateOperation. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Adds --strict CLI option, CandleConfig.Strict property, and TenantConfiguration.Strict, wired through tenant initialization for R4/R4B/R5. Default false preserves existing lenient behavior; the flag is unread until Phase 2 (PUT empty-body-id handling). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
When --strict is off (default), PUT /<Type>/<id> with empty body Resource.id stamps the URL id onto the body (preserves historical lenient behavior). When --strict is on, the same request is rejected with 422 + OperationOutcome (IssueType.Required) per FHIR R4 spec section 3.1.0.7. Adds three test rows in FhirStoreTests: PutEmptyBodyIdLenientDefaultStampsUrlId (lenient default fixture) plus a new TestPostPutIdSemanticsStrict fixture covering PutEmptyBodyIdStrictRejectsWith422, PutAcceptsUrlBodyIdMatchInStrict, and PutRejectsUrlBodyIdMismatchInStrict, all cross-version (R4/R4B/R5). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ler integration tests Deletes the isParametersWireType branches from FhirController.GetTypeOperation and PostTypeOperation. /<tenant>/Parameters/$<op> URLs no longer special-route to system-level dispatch; they now go through normal type-level dispatch (which 404s, since Parameters is not in the resource store) — Parameters as a URL resource segment is wrong-by-design per FHIR REST. Operations that accept Parameters-wrapped bodies handle the unwrap themselves (OpValidate.ExtractParametersResource). Adds Program.BuildAppForTesting as a minimal additive helper that builds a WebApplication for in-process integration testing of FhirController, plus a Microsoft.AspNetCore.TestHost-based CandleWebApplicationFactory and ValidateRoutingTests covering the positive type-level case (/fhir/r4/Patient/$�alidate -> 200 + OperationOutcome) and the negative Parameters-as-URL case (/fhir/r4/Parameters/$�alidate -> 404). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… guard, style
H2 (target resolution): body wins over focus at instance level. Parameters-wrapped body unwraps to the embedded resource; direct (non-Parameters) Resource body is used as-is; otherwise the stored focus is used. Closes the precedence bug where a body-supplied candidate was silently ignored when a focus was present.
M1 (mode/profile characterization): when a Parameters body contains 'mode' or 'profile' parameters, append a documented Information OperationOutcome issue per parameter ('Parameter X is currently ignored by this validate implementation.') so the no-op is visible to clients.
M7 (init guard): _inspector init wrapped in a try/catch that logs once and leaves the field as null. DoOperation now returns a clear 500 + Error OperationOutcome if the inspector failed at startup, instead of letting a TypeInitializationException take down every validate call.
N1 (style): switched List<T>() initializers to [].
Adds TestValidateOperation rows: ValidateInvalidResourceReturnsErrorIssues (rewritten), ValidateMissingRequiredFieldReturnsErrorIssue, ValidateInstanceLevelWithBodyValidatesBody, ValidateInstanceLevelWithoutBodyValidatesFocus, ValidateModeParameterEmitsInformationIssue, ValidateProfileParameterEmitsInformationIssue, ValidateEmptyBodyReturnsOperationOutcome.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The DoInstanceCreate gate that decides whether a client-supplied Resource.id is preserved now keys on ctx.HttpMethod == 'POST' instead of ctx.Interaction == TypeCreate*. This is the spec-correct semantic per FHIR REST section 2.42: the gate is the HTTP method, not the dispatcher interaction enum. forceAllowExistingId callers (bundle ingest, load-from-disk update-as-create) still override. Updates the OperationDefinition self-registration call (VersionedFhirStore.cs:441) to pass forceAllowExistingId: true so internal POSTs of OperationDefinitions during startup preserve the computed canonical-derived id (otherwise M2 would discard it). Adds BundleTransactionPostStillSucceedsAfterM2Flip to TestPostPutIdSemantics, a cross-version regression test confirming bundle transactions still process POST entries correctly post-M2 (and documenting that bundle preprocessing unconditionally reassigns POST-entry ids per FHIR section 3.2.0.16.5, so forceAllowExistingId is a no-op on that path). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…dary rows M3 — adds PatientSearchNotModifierMatchesAbsentElement to R4BTestsPatient and R5TestsPatient. Inline-creates a Patient with no gender element, asserts gender:not=male returns (existing) + 1 and the response references the new patient by id, then deletes it. The existing fixture-count rows could silently pass even if the implementation excluded absent gender; this new test actively exercises the absent-element branch. M4 — adds ObservationSearch date offset boundary rows (date=ge2017-05-03T19:54:26Z and date=lt... ) to R4/R4B/R5 to pin the ToUniversalTime() normalization for an effectiveDateTime stored with a -04:00 offset. M6 — adds ObservationSearch instant-boundary rows (date=gt2016-05-18T22:33:22Z and date=ge...) to R4/R4B/R5 to pin gt/ge inclusion semantics at an observation's instant. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Adds PutReplaceExistingWithMatchingIdsSucceeds to TestPostPutIdSemantics: POSTs a Patient, then PUTs to the same id with a mutated body (added telecom). Asserts the PUT succeeds, the response carries the new content, and a subsequent read returns the updated resource. Closes the M5 coverage gap where PutAcceptsUrlBodyIdMatch only exercised PUT-as-create. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
M8 — string-search values are now case- and accent-folded once at ParsedSearchParameter.ProcessTypedValues parse time and cached in a new FoldedValues field, eliminating per-resource refolding in the hot path of EvalStringSearch.TestStringStartsWith / TestStringContains and the HumanName / Address variants. FoldForSearch was relocated from EvalStringSearch (private) to ParsedSearchParameter (internal static) so the parser and evaluators share one implementation.
M9 — entries whose folded form is the empty string are stored as null in FoldedValues; evaluators skip null entries. Closes the StartsWith('') / Contains('') false-positive that would match every resource for inputs like a bare combining mark (e.g., name=\\u0301).
Adds R4 PatientSearch rows (name=\\u0301 and name:contains=\\u0301 -> 0 results) that pin the M9 contract.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
L2 — the ap-prefix length-guard fallback (TimeSpan.FromDays(1)) is now a Debug.Assert against the producer invariant. ParsedSearchParameter.ProcessTypedValues allocates ValueDateApproxDeltas at the same length as Values for every Date parameter, so the fallback was dead in practice. Debug.Assert catches future producer-side bugs without paying a per-call branch in Release. L5 — the YYYY-MM ap window widens from 62 to 65 days. 62 days is the worst-case length of two consecutive months (Jul-Aug, Dec-Jan); 65 adds a small safety margin for timezone offsets and inclusive-boundary edge cases. The docstring documents the AddMonths(2) alternative was rejected to avoid leap-Feb asymmetry. Adds PatientSearchApYYYYMMWindowIncludesSafetyMargin to R4TestsPatient: inline-creates a Patient born 2016-01-28 and queries birthdate=ap2016-04. The 65-day window includes the birthDate; a 62-day window would exclude it. Pins the L5 change. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…nings Adds RawAllowExistingId, RawAllowCreateAsUpdate, and RawSupportNotChanged properties on CandleConfig that preserve null when neither CLI nor env supplied a value. Used exclusively by upcoming startup conflict-warning logic that needs to distinguish an explicit user value from an unsupplied flag when composing with --strict. Existing non-nullable AllowExistingId, AllowCreateAsUpdate, and SupportNotChanged properties keep their ?? default semantics unchanged, so no runtime callers are affected. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Introduces the Foundation infrastructure for --strict: - FhirCandle.Strict.StrictRuleCode enum + StrictRule registry exposing per-FHIR-version canonical spec URLs (http://hl7.org/fhir/R{4,4B,5}/...). Anchors verified against the local spec mirrors. - TenantConfiguration.ResolveStrict() composes --strict into the related per-feature flags (AllowExistingId, AllowCreateAsUpdate -> false). Called from VersionedFhirStore.Init so direct programmatic callers (tests, library users) get the same authoritative composition as CLI-launched servers, not only at the CLI boundary. Idempotent. - SerializationUtils.BuildOutcomeForStrictRule / *Rules helpers emit OperationOutcomes whose diagnostics include the resolved spec URL so test-rig operators can justify rejections without out-of-band lookup. - Program.BuildTenantConfigurations now calls ResolveStrict() per tenant, logs a one-line strict banner, and emits per-override conflict warnings (via the new ComputeStrictWarnings helper) when --strict is paired with an explicit conflicting --create-existing-id / --create-as-update value. ComputeStrictWarnings is unit-tested. - StrictModeStartupTests covers ResolveStrict idempotency / no-op, ComputeStrictWarnings empty / conflict cases, and per-rule per-version URL resolution. No production-behavior changes yet; rule enforcement lands in Phase 2/3. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Phase 2 of the --strict Foundation slice. Adds spec-correct status-code short-circuits to DoInstanceCreate / DoInstanceUpdate that composition on AllowExistingId / AllowCreateAsUpdate alone does not provide: - POST with client-supplied Resource.id under --strict returns 400 (PostClientSuppliedId rule); composition alone would silently re-ID the resource. Internal bundle/load callers still pass forceAllowExistingId: true to bypass. - PUT on a missing resource id under --strict returns 404 (PutCreateAsUpdateDisallowed rule); composition via AllowCreateAsUpdate=false alone returns 400. Skipped for conditional PUTs (no URL id segment) and load-from-disk callers (_loadState != None). - Ill-formed URL or body Resource.id under --strict returns 400 (ResourceIdRegex rule). Firely's BACKWARDSCOMPATIBLE deserializer pre-empts at 422 for JSON bodies with non-conforming ids; the check remains a defensive guard for URL ids and other paths. - Existing always-on PUT id-mismatch (422) and strict empty-body-id (422) checks now route through SerializationUtils.BuildOutcomeForStrictRule so OperationOutcome.diagnostics carries the canonical spec URL. New test fixtures: - TestStrictModeCompositionWins (cross-version) — pins that constructing TenantConfiguration with Strict=true AND explicit AllowExistingId=true / AllowCreateAsUpdate=true ends up with the flags forced to false after Init. Closes the composition-leak path called out in the plan. - TestStrictModeIdSemantics — POST/PUT strict id rules, parameterized across R4/R4B/R5. Also pins meta.versionId / meta.lastUpdated server- assignment behavior (no new enforcement; lenient and strict behave the same here per feature request line 150). - TestLenientModeIdSemantics — lenient defaults still hold (PUT-on- missing creates, mismatch rejected with 422). The existing TestPostPutIdSemanticsStrict fixture drops its now-redundant explicit AllowExistingId / AllowCreateAsUpdate setup (ResolveStrict in Init flips them anyway) and the PutAcceptsUrlBodyIdMatchInStrict test seeds the resource via POST + forceAllowExistingId before the PUT, so the strict PUT-on-missing rule does not pre-empt it. Full test suite: 929 passed, 0 failed. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…own/malformed)
Phase 3 of the --strict Foundation slice. Surfaces unknown and malformed
search parameters with 400 + multi-issue OperationOutcome when strict
search handling is in effect, instead of silently dropping them.
- ParsedSearchParameter.Parse gains an overload that returns the list
of unknown query-string keys via an out List<string>. Drops the silent
Console.WriteLine on unknown keys; the existing single-arg signature
is preserved as a thin shim for the many internal callers (subscription
triggers, OperationDefinition self-registration, auth filtering,
bundle reference resolution, compartment-filter synthesis) that
should stay on lenient behavior even under --strict.
- VersionedFhirStore gains two private helpers:
- EffectiveSearchHandling(ctx) — explicit Prefer: handling=strict /
handling=lenient wins (last directive across all header values);
otherwise the tenant default (_config.Strict → Strict, else Lenient).
- TryBuildStrictSearchOutcome(handling, unknownParameters, parameters,
out response) — aggregates SearchUnknownParameter issues from the
unknown-keys list and SearchMalformedParameter issues from
parameters.IgnoredParameter and per-value IgnoredValueFlags[i] (so
partially-malformed multi-values like ?birthdate=2020-01-01,bogus
are caught), and returns a 400 with one issue per finding.
- DoTypeSearch, DoSystemSearch, and DoCompartmentSearch wire the
out-overload Parse + strict short-circuit into their user-facing
parameter parsing. Compartment-filter synthesis (the internal
?spCode=Type/Id parses) stays on the legacy signature.
New StrictSearchHandlingTests fixture parameterized across R4/R4B/R5
covers seven scenarios: unknown param, multi-unknown, malformed date,
partial multi-value malformation, explicit Prefer: lenient overriding
strict tenant, explicit Prefer: strict overriding lenient tenant, and
lenient regression. 950/950 tests pass.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Adds CapabilityStatementUrlMatchesTenantBaseUrlStrict and CapabilityStatementFhirVersionMatchesTenantStrict to the TestStrictModeIdSemantics fixture (parameterized across R4/R4B/R5). No production change — generateCapabilities already emits the correct url (tenant base + /CapabilityStatement/metadata) and fhirVersion (4.0.1 / 4.3.0 / 5.0.0 via CommonToFirelyVersion). These tests pin the behavior so a future refactor cannot regress it under --strict. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Phase 5 of the --strict Foundation slice. - CandleConfig.Strict CLI Description now names the full Foundation scope (id semantics + search handling), the per-flag override behavior, and the spec-URL diagnostics convention. - README gains a "Strict Mode" subsection under FHIR Tenants with a table of enforced rules, their status codes, the lenient default, and the spec section each rule cites. Also documents Prefer: handling override semantics and explicit non-advertisement in CapabilityStatement. No production-behavior change. Full suite 956/956 pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Rewrite four ShouldContainKey assertions to ContainsKey(...).ShouldBeTrue() so they no longer bind to Shouldly's IDictionary<,>-only overload, which is the sole overload exposed on net8.0. Restores multi-target Release compile. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…embly Replace the obsolete ModelInspector.ForAssembly(typeof(Patient).Assembly) call with the prescribed ModelInfo.ModelInspector, clearing CS0618 across the R4/R4B/R5 emissions of the shared OpValidate.cs. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Remove the explicit net8.0/net9.0 Microsoft.AspNetCore.App FrameworkReference items; the Web SDK already provides them implicitly, clearing NETSDK1086. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Remove the three TFM-conditional Microsoft.AspNetCore.Components.Web PackageReferences from the shared fhir-candle-ui.props; the Razor/Web SDK already supplies the assembly, clearing NU1510 across all five importers. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add docs/ landing page, docs/user/ and docs/technical/ index pages, and stub content pages so index links resolve. Content is filled in later phases. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Move getting-started, tenants, loading-data, strict-mode, subscriptions-ri, and OpenTelemetry detail into dedicated user pages. README still retains the originals; they are trimmed in a later phase. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Document scope, limitations, invocation levels, target resolution, response semantics, and three worked curl examples. Behavior derived from OpValidate.cs. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Document the shared-project pattern, extern aliases, plugin discovery, storage model, build/test workflow, coding conventions, and the \ implementation note. Facts derived from source and copilot-instructions. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replace relocated README sections (tenants, loading data, strict mode, subscriptions RI, OpenTelemetry, clone/build detail) with a Documentation pointer and concise link summaries. No detail dropped; it now lives in docs/. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…e-query, and subscription operations Add user references for the $convert, $test-if-fhir, $reset-store, $feature-query, $status, $events, and $subscription-hook operations, matching the validate.md style. Add an operations index and link all operations from the user docs index. Behavior derived from the Op* sources. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
GinoCanessa
added a commit
that referenced
this pull request
Jul 7, 2026
Add the four published-but-unrecorded NuGet versions (v2026.526.2054/#50, v2026.527.1737/#52, v2026.528.2036/#58, v2026.623.2024/#60) as reverse-chronological entries above v2026.415.1643, with grouped thematic bullets and external-contributor credit. The relocated issue #40 backport-IG link fix is written into v2026.528.2036. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
GinoCanessa
added a commit
that referenced
this pull request
Jul 7, 2026
Replace the stale issue #41 backport-IG bullet (which actually shipped in v2026.528.2036 as the #40 fix) with the four genuinely-unreleased post-#60 dev store fixes: type-level delete search criteria, conditional update interaction gating, control-only POST create handling, and the control-only write parse-lock (issue #62). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This PR brings fhir-candle closer to spec-correct REST behavior so it can serve as the system-under-test for FHIR conformance rigs (Touchstone, Inferno, IG test plans). It adds an opt-in strict mode, implements the
$validateoperation, fixes a batch of search and storage correctness bugs, and cleans up redundant build/package references.Highlights
🔒 Strict mode (
--strict, default off)A monolithic, opt-in, per-tenant flag that flips on the server''s strictest spec-conformant posture. Rejected requests carry the relevant FHIR spec URL inline in
OperationOutcome.diagnostics.--strictoverrides a conflicting explicit per-feature flag (strict wins).idsemantics on POST/PUT: client-supplied id, id regex ([A-Za-z0-9\-\.]{1,64}), and on-missing behavior.PUTempty-body-id and body-id ≠ URL-id enforcement.Prefer: handling=strict/lenientsearch enforcement — explicit header wins over tenant default; unknown/malformed params return400instead of being silently dropped.CapabilityStatement— conformance clients shouldn''t need out-of-band metadata.✅
$validateoperationOpValidateimplementing the$validateoperation, refactored to useModelInfo.ModelInspector(over the obsoleteForAssembly).CandleWebApplicationFactory+ValidateRoutingTests).🔍 Search correctness & performance
apprefix (65-dayYYYY-MMwindow);:missing=trueno longer crashes on date params.:notmodifier now matches resources missing the element.💾 Storage semantics
🧱 Build / dependencies
AspNetCore.Components.Webpackage refs and redundantAspNetCore.AppFrameworkReferences.Testing
Adds extensive coverage:
StrictModeStartupTests,StrictSearchHandlingTests,ValidateRoutingTests, expandedFhirStoreTests(~1.3k lines), and new R4/R4B/R5 fixture data. Run with:dotnet test --configuration Release --framework net10.0 --no-restore --verbosity normal