Skip to content

feat: strict spec-conformance mode, $validate operation, and search/storage correctness fixes - #60

Merged
GinoCanessa merged 35 commits into
mainfrom
dev
Jun 23, 2026
Merged

GinoCanessa merged 35 commits into
mainfrom
dev

Conversation

@GinoCanessa

Copy link
Copy Markdown
Collaborator

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 $validate operation, 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.

  • Rule registry with per-tenant composition and startup warnings when --strict overrides a conflicting explicit per-feature flag (strict wins).
  • id semantics on POST/PUT: client-supplied id, id regex ([A-Za-z0-9\-\.]{1,64}), and on-missing behavior.
  • PUT empty-body-id and body-id ≠ URL-id enforcement.
  • Prefer: handling=strict / lenient search enforcement — explicit header wins over tenant default; unknown/malformed params return 400 instead of being silently dropped.
  • Raw nullable CLI value capture to detect strict-vs-explicit-flag conflicts.
  • Not advertised in CapabilityStatement — conformance clients shouldn''t need out-of-band metadata.
  • README section + rule table documenting every enforced behavior, status code, lenient default, and spec link.

✅ $validate operation

  • New OpValidate implementing the $validate operation, refactored to use ModelInfo.ModelInspector (over the obsolete ForAssembly).
  • Target precedence, mode/profile char validation, and init guard fixes.
  • Controller integration tests (CandleWebApplicationFactory + ValidateRoutingTests).

🔍 Search correctness & performance

  • Spec-correct FHIR date prefix interval semantics; precision-aware window for ap prefix (65-day YYYY-MM window); :missing=true no longer crashes on date params.
  • Accent-insensitive default string search; cache folded string-search values and skip empty folds (perf).
  • Token :not modifier now matches resources missing the element.
  • Regression test for repeated-param AND semantics.

💾 Storage semantics

  • POST always assigns a server id; PUT enforces URL/body id match.
  • POST id-assignment now keys on HTTP method.
  • PUT-replace happy-path test coverage.

🧱 Build / dependencies

  • Drop SDK-provided AspNetCore.Components.Web package refs and redundant AspNetCore.App FrameworkReferences.
  • Dependency updates.

Testing

Adds extensive coverage: StrictModeStartupTests, StrictSearchHandlingTests, ValidateRoutingTests, expanded FhirStoreTests (~1.3k lines), and new R4/R4B/R5 fixture data. Run with:

dotnet test --configuration Release --framework net10.0 --no-restore --verbosity normal

GinoCanessa and others added 30 commits June 2, 2026 10:18
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>
GinoCanessa and others added 5 commits June 23, 2026 13:46
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
GinoCanessa merged commit 6e5164a into main Jun 23, 2026
3 checks passed
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant