Skip to main content

Diagnostics

This page explains how FLASH reports problems (parse errors, definition errors, evaluation/validation issues) and how severity thresholds affect whether an issue throws, logs, or is returned in a diagnostic report.

If you’re still learning rule forms and indentation, start with Syntax. If you’re trying to predict append/override or input fan-out behavior, see Evaluation model.

Diagnostic sources

Diagnostics come from multiple stages of the FLASH pipeline. Knowing which stage you’re in usually tells you what to change.

Parse-time errors (syntax and indentation)

Parse-time errors happen when FLASH can’t be parsed into declarations and rules.

  • These errors prevent evaluation.
  • They tend to point at a specific line/indentation level or a malformed rule form.

Examples of parse-time issues include missing declarations (like InstanceOf:), malformed rule lines, or indentation that isn’t in 2-space increments.

Definition-resolution errors (FHIR structure navigation)

After parsing, FLASH resolves the output type and every rule path against the active FHIR definition set.

  • If no Structure Navigator (or definition set) is available, FLASH cannot perform conformance-driven resolution.
  • If a path segment doesn’t exist on the resolved type, the block fails before any rule writes happen.

This category includes “unknown type/profile” and “invalid element path” failures.

Evaluation and validation diagnostics (type checks, regexes, cardinality, bindings)

Once a rule path resolves, FLASH evaluates the right-hand-side expression and applies a write. Conformance-aware shaping and validation can emit additional diagnostics, such as:

  • assigning a primitive where a complex object is required
  • a primitive value that fails a FHIR regex constraint
  • required (min=1) elements that were never populated

Many validation diagnostics are in the F5xxx code family and are controlled by the threshold model described below.

User-raised diagnostics and logs

Some diagnostics are intentionally raised by mappings (for example, $warn, $info, and $trace functions). These are still subject to logging/collection thresholds, and are easiest to see via verbose evaluation.

Threshold model

FLASH uses numeric severity to decide what happens to a diagnostic.

  • Lower numbers are more critical.
  • Threshold comparisons are exclusive: an issue triggers an action when severity < threshold.

Severity bands

For policy-governed FLASH validation diagnostics (F5xxx), the severity is derived from the code itself:

  • F5xyy has severity xy (for example, F5110 has severity 11).

For everything else (including JSONata codes like S0201 and non-F5 FLASH codes like F1006), the policy treats the severity as fatal.

Common level boundaries:

  • 0 = fatal
  • 10 = invalid
  • 20 = error
  • 30 = warning
  • 40 = notice
  • 50 = info
  • 60 = debug

Thresholds

The evaluation environment provides four thresholds:

  • throwLevel: issues with severity < throwLevel are treated as “throwing” in non-verbose evaluation.
  • logLevel: issues with severity < logLevel are emitted to the runtime logger.
  • collectLevel: issues with severity < collectLevel are collected into the per-evaluation diagnostic bag.
  • validationLevel: controls whether certain validations run and whether a diagnostic is enforceable.
    • When an issue’s severity is not within the validation band, the issue is treated as inhibited: it can be collected, but it does not log or throw.

Default thresholds (when your runtime doesn’t override them):

  • throwLevel = 30 (fatal/invalid/error throw; warnings do not)
  • logLevel = 40 (fatal/invalid/error/warning log)
  • collectLevel = 70 (everything is eligible to be collected)
  • validationLevel = 30 (fatal/invalid/error validations are enforced)

Raising a threshold makes behavior stricter by including less-critical bands (for example, setting throwLevel to 40 makes warnings eligible to throw).

evaluate vs evaluateVerbose

Most integrations expose two evaluation modes:

  • Non-verbose evaluation (evaluate) returns a result on success, and may throw when an enforceable diagnostic is in the throwing band.
  • Verbose evaluation (evaluateVerbose) returns a report that includes both the result (when available) and a diagnostics bag; it does not throw for handled FLASH/JSONata issues.

Verbose evaluation is the fastest way to debug because you can see:

  • which diagnostics were produced
  • which ones were collected (affected by collectLevel)
  • whether any collected issues are in the throwing band (affected by throwLevel)

Representative codes

This section lists a small set of common diagnostics. It is not a complete code catalog.

F1000 — No Structure Navigator provided

Meaning: your expression contains FLASH blocks, but evaluation has no FHIR Structure Navigator / definition set available, so FLASH can’t resolve types and paths.

Typical causes:

  • the runtime is executing “JSONata only” without a configured FHIR navigator
  • the navigator was not wired into the evaluation environment

What to do:

  • Ensure your runtime config provides a Structure Navigator loaded with the FHIR definitions your mapping expects.

F1006 — Malformed FLASH rule

Meaning: a line inside a FLASH block does not match any valid rule form.

Example (missing *):

InstanceOf: Patient
active = true

Fix:

InstanceOf: Patient
* active = true

F1007 — Missing InstanceOf:

Meaning: a FLASH block is missing the required InstanceOf: declaration.

Example:

* active = true

Fix:

InstanceOf: Patient
* active = true

F2002 — Invalid element path

Meaning: the rule path contains a segment that does not exist on the resolved FHIR type/profile.

Example:

InstanceOf: Patient
* madeUpElement = "nope"

What to do:

  • Check that every path segment exists on the target type.
  • If you intended a choice-type element, select a concrete JSON name (for example, write valueString or valueQuantity rather than value[x]).
  • If you intended a repeating element, confirm you’re using a nested/context rule or the correct element name for the repeating container.

F5104 — Complex object expected, primitive assigned

Meaning: the target element’s type is complex (object-shaped), but the assigned value is a primitive.

Example (assigning a string to name, which is a complex HumanName):

InstanceOf: Patient
* name = "ExampleCare"

Fix by targeting a primitive child:

InstanceOf: Patient
* name
* family = "ExampleCare"
* given = "Avery"

F5110 — Primitive fails a FHIR regex constraint

Meaning: a primitive string value does not match the regex constraint for the target element’s type.

Example (FHIR Resource.id has a strict pattern; spaces are not allowed):

InstanceOf: Patient
* id = "ExampleCare Patient 001"

What to do:

  • Use a valid id token (for example, ExampleCare-Patient-001).
  • If the value comes from input, normalize it in JSONata before assignment.

F5130 — Mandatory element missing

Meaning: the resolved definition says an element has a minimum cardinality (min) greater than 0, but no value was provided.

Example (an Observation is missing required elements like status and code):

InstanceOf: Observation
* valueString = "ExampleCare note"

Fix by populating the required fields:

InstanceOf: Observation
* status = "final"
* code.text = "ExampleCare note"
* valueString = "ExampleCare note"

Debugging workflows

“I get undefined result”

  1. Check for fatal/parse/definition diagnostics first (these prevent meaningful output).
  2. If you can, run the same mapping through verbose evaluation and inspect the collected diagnostics.
  3. If there are no critical diagnostics, look for rules whose right-hand side evaluates to undefined (those rules perform no write).

When the issue is “no writes happened”, start with one simple assignment that is known-good for the target type (for example, * active = true on Patient) to confirm that the block is being evaluated.

“My rule doesn’t write anything”

Common causes:

  • The path doesn’t resolve (look for definition-resolution codes like F2002).
  • The expression evaluates to undefined for the current input context (no write occurs).
  • The value is the wrong shape for the target type (look for F51xx diagnostics like F5104).

If the rule is inside an input-context fan-out block (* (<expr>).<path>), confirm what <expr> evaluates to (object vs array vs undefined).

“It throws only in one environment”

When a mapping behaves differently between environments, the usual causes are:

  • Different FHIR definition sets (different packages/profiles loaded by the navigator).
  • Different threshold configuration (for example, a higher throwLevel or validationLevel makes more issues enforceable).
  • Different runtime integrations (for example, one environment uses verbose evaluation and the other uses non-verbose evaluation).

To isolate the difference:

  1. Run verbose evaluation in both environments.
  2. Ensure collectLevel is high enough to include any issue that could be throwing (a common safe choice is collectLevel >= throwLevel).
  3. Compare diagnostics by code (the code is the stable identifier; messages can include contextual details).