# `Diffo.Provider.Calculations.InheritedCharacteristic`
[🔗](https://github.com/diffo-dev/diffo/blob/v0.9.0/lib/diffo/provider/components/calculations/inherited_characteristic.ex#L5)

Backing calculation for `inherited_characteristic` DSL declarations.

Walks the graph along a normalised `via:` hop chain (assignment and/or relationship
edges, in either direction — see `Diffo.Provider.Calculations.Traversal`), reads the
typed characteristic at the declared `read` role on each reached instance, optionally
renames it (`as`) and collapses the result to one end (`collapse`).

Injected automatically by `TransformInheritedRefs` — do not reference this module
directly; use the `inherited_characteristic` DSL entity inside `characteristics do`.

## Cross-world resolution

Unlike `inherited_place` / `inherited_party` (which query universal `PlaceRef` /
`PartyRef`), the typed characteristic module varies per reached resource. This calc
resolves it at runtime via `AshNeo4j.worlds/1` on each struct and
`Diffo.Provider.Extension.Info.provider_characteristics/1` on its outermost resource.
Late-binding by design — the reached resource may not exist at the consumer's compile
time.

## Result shape

Without `collapse`, a list per input record — one entry per reached instance. Each entry
is:

- A `BaseCharacteristic`-derived record (or a list of such records when the reached
  resource's characteristic declaration is `{:array, M}`).
- `%Diffo.Unknown{}` when the instance can't be projected to a loadable resource module,
  or its module declares no characteristic at the `read` role.

With `collapse: :first | :last`, a single such entry or `nil` (empty result).

When `as` is set, each `BaseCharacteristic` record is renamed to that name (both the
loaded value and, via surfacing, the encoded TMF entry); `%Diffo.Unknown{}` sentinels are
left untouched.

## Reason vocabulary (local to this world)

- `:no_concrete_world` — `AshNeo4j.worlds/1` returned `[]`; the reached struct has no
  labels resolvable to a loaded `AshNeo4j.DataLayer` resource. `:context` carries
  `%{source_id: id}`.
- `:role_not_declared` — the outermost world's resource exists but its
  `provider_characteristics/1` has no entry for the `read` role. `:context` carries
  `%{source_id: id, resource: module, role: atom}`.

Per the **Cross-domain lookups** AGENTS.md section, reasons are world-local — consumers in
other worlds should treat them as opaque and (if composing) wrap via `:inner_unknown`.

# `describe`

# `has_calculate?`

# `has_expression?`

# `init`

# `strict_loads?`

---

*Consult [api-reference.md](api-reference.md) for complete listing*
