From dd8e5c5f67ce7ef178028f000e1c3447fbc5b320 Mon Sep 17 00:00:00 2001 From: Matt Beanland Date: Wed, 20 May 2026 23:06:08 +0930 Subject: [PATCH] update docs and guidance --- AGENTS.md | 32 +++- .../use_diffo_provider_extension.livemd | 108 ++++++++++++++ .../calculations/field_from_assignment.ex | 29 +++- .../field_via_assigned_relationship.ex | 27 +++- .../calculations/field_via_relationship.ex | 34 ++++- .../calculations/inherited_party.ex | 11 +- .../calculations/inherited_place.ex | 11 +- .../extension/inherited_party_declaration.ex | 27 +++- .../extension/inherited_place_declaration.ex | 27 +++- usage-rules.md | 138 ++++++++++++++++++ 10 files changed, 434 insertions(+), 10 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 58c0036..3a359a0 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -51,7 +51,10 @@ lib/diffo/provider/ relationship_step.ex # RelationshipStep struct — pipeline step for relationships do persisters/ # Terminal bakers — run after all transformers; only read DSL state and bake module functions transformers/ - transform_relationships.ex # TransformRelationships — resolves relationships pipeline, bakes permitted_source_roles/0 and permitted_target_roles/0 + transform_relationships.ex # TransformRelationships — resolves relationships pipeline, bakes permitted_source_roles/0 and permitted_target_roles/0 + transform_inherited_refs.ex # TransformInheritedRefs — injects calculations for inherited_place/inherited_party declarations + inherited_place_declaration.ex # DSL entity struct for inherited_place + inherited_party_declaration.ex # DSL entity struct for inherited_party verifiers/ verify_relationships.ex # Verifies relationship role declarations are atoms validations/ @@ -69,8 +72,13 @@ lib/diffo/provider/ assignment_relationship.ex # AssignmentRelationship — pool assignment relationship with top-level pool/thing/value/alias scalar attributes relationship.ex # Relationship — mutable TMF service/resource relationship with graph Characteristic nodes calculations/ - characteristic_value.ex # Calculation: builds .Value TypedStruct from record fields - assigned_values.ex # Calculation: returns list of assigned integers for a pool+thing + characteristic_value.ex # Calculation: builds .Value TypedStruct from record fields + assigned_values.ex # Calculation: returns list of assigned integers for a pool+thing + inherited_place.ex # Calculation: backing impl for inherited_place DSL + inherited_party.ex # Calculation: backing impl for inherited_party DSL + field_from_assignment.ex # Calculation: field from AssignmentRelationship record + field_via_assigned_relationship.ex # Calculation: field from source instance via assignment traversal + field_via_relationship.ex # Calculation: field from target instance via DefinedSimpleRelationship instance/extension.ex # Thin marker (sections: []) — kind identification party/extension.ex # Thin marker place/extension.ex # Thin marker @@ -147,6 +155,10 @@ provider do places do place :installation_site, MyApp.GeographicSite place_ref :billing_address, MyApp.GeographicAddress + # Inherited — generates a calculation that traverses AssignmentRelationship + # by alias and reads PlaceRef from the source instance. No PlaceRef edge is created. + inherited_place :exchange, source_role: :location # alias = role name (single-hop default) + inherited_place :nni_site, via: [:uplink], source_role: :location # explicit alias end behaviour do @@ -350,3 +362,17 @@ not. Add any useful hypotheses as a follow-up comment on the issue, then leave i - Using `Ash.Resource.Change` for pure permission or constraint checks — anything that only decides valid/invalid with no side effects belongs in `Ash.Resource.Validation`, not a change. Changes are for mutations. +- Using `inherited_place` or `inherited_party` without an assignment alias in place — the + traversal filters by alias; if the assignment was created without an alias (or with a + different alias), the calculation returns an empty list. Ensure the `alias:` field on + `Assignment` matches the declared role (or the `via:` step) before expecting results. +- Referencing `Diffo.Provider.Calculations.InheritedPlace` or `InheritedParty` directly in + `calculations do` — these are internal modules injected by the transformer. Use the + `inherited_place` / `inherited_party` DSL entities in `places do` / `parties do` instead. +- Reaching for `FieldViaRelationship` to traverse an `AssignmentRelationship` — that module + traverses `DefinedSimpleRelationship` (forward, source → target). For assignments + (reverse, target → source) use `FieldViaAssignedRelationship` or `FieldFromAssignment`. +- Querying `FieldViaRelationship` without supplying `alias:` or `type:` — a source instance + typically has many forward `DefinedSimpleRelationship` records pointing to unrelated things. + Without at least one filter the result is a noisy mix. Always supply `alias:`, `type:`, or + both. diff --git a/documentation/how_to/use_diffo_provider_extension.livemd b/documentation/how_to/use_diffo_provider_extension.livemd index 7094589..20e7773 100644 --- a/documentation/how_to/use_diffo_provider_extension.livemd +++ b/documentation/how_to/use_diffo_provider_extension.livemd @@ -494,6 +494,114 @@ defmodule Diffo.Compute.GPU do end ``` +## Aliases, Inherited DSL, and Field Calculations + +### Aliases on assignment slots + +Every `AssignmentRelationship` carries an optional `:alias` — an atom given to a slot by +the consuming (target) side before or when the assignment is bound. Think of it as a stable +name for the slot: the consumer says "I have a slot called `:primary_gpu`", and the producer +assigns into it carrying `alias: :primary_gpu`. The alias never changes, even if the +assignment is recreated. + +Pass the alias via `Assignment.alias` when assigning: + +```elixir +# Assign a core from gpu_1 into cluster_1's :primary_gpu slot +assignment = %{ + assignment: %Assignment{ + assignee_id: cluster_1.id, + operation: :auto_assign, + alias: :primary_gpu + } +} +gpu_1 = Compute.assign_gpu_core!(gpu_1, assignment) +``` + +The identity constraint `[:target_id, :alias]` on `AssignmentRelationship` guarantees at +most one assignment per (cluster, alias) pair — the `:primary_gpu` slot can only hold one +assignment at a time. + +### Inheriting a place from an assigned resource + +A service or resource can declare that it inherits a place from the instance that assigned +something to it — without creating its own `PlaceRef` edge. The `inherited_place` DSL entity +in `places do` generates an Ash calculation that traverses the assignment graph at read time. + +In our Compute example: if a `GPU` instance has a `:data_centre` place, and a `Cluster` +wants to surface the data centre of its primary GPU, it can declare: + +```elixir +provider do + places do + # Traverses AssignmentRelationship where alias = :primary_gpu, + # reads PlaceRef with role :data_centre from the source GPU instance. + inherited_place :primary_data_centre, via: [:primary_gpu], source_role: :data_centre + end +end +``` + +Load it like any other calculation: + +```elixir +cluster = Ash.load!(cluster_1, [:primary_data_centre], domain: Compute) +# cluster.primary_data_centre => [%DataCentre{...}] +``` + +`inherited_party` works identically for party inheritance: + +```elixir +# Cluster inherits the operating Tenant from the GPU it was assigned from +provider do + parties do + inherited_party :operator, via: [:primary_gpu], source_role: :operator + end +end +``` + +### Reading fields from the assignment graph + +Three calculation modules handle common traversal patterns. All return lists. + +**`FieldFromAssignment`** — reads a field directly from the `AssignmentRelationship` +record. Use it for values that live on the relationship itself: `:value`, `:pool`, +`:thing`, `:alias`. + +```elixir +# Core number assigned to this cluster under the :primary_gpu slot +calculate :primary_core, {:array, :integer}, + {Diffo.Provider.Calculations.FieldFromAssignment, [alias: :primary_gpu, field: :value]} +``` + +**`FieldViaAssignedRelationship`** — traverses assignment in reverse (cluster → GPU) +and reads a field from the source instance. Use it for fields that live on the assigning +resource, not the relationship. + +```elixir +# Name of the GPU holding the :primary_gpu slot on this cluster +calculate :primary_gpu_name, {:array, :string}, + {Diffo.Provider.Calculations.FieldViaAssignedRelationship, + [via: [:primary_gpu], field: :name]} +``` + +**`FieldViaRelationship`** — traverses `DefinedSimpleRelationship` in the forward +direction (source → target) filtered by `alias:` and/or `type:`. Use it when this +instance is the *source* of a named forward relationship. + +```elixir +# Name of the downstream node this GPU provides to +calculate :downstream_name, {:array, :string}, + {Diffo.Provider.Calculations.FieldViaRelationship, + [type: :assignedTo, alias: :downstream, field: :name]} +``` + +| I want… | Use | +|---------|-----| +| Value on the assignment record (`:value`, `:pool`) | `FieldFromAssignment` | +| Field from the instance that assigned to me | `FieldViaAssignedRelationship` | +| Field from an instance I have a forward relationship to | `FieldViaRelationship` | +| Place/party inherited via assignment | `inherited_place` / `inherited_party` | + ## Party Extension `Diffo.Provider.BaseParty` is an Ash Resource Fragment for domain-specific Party kinds, mirroring `BaseInstance`. It provides common Party attributes — `id`, `href`, `name`, `type`, `referred_type` — and the unified `Diffo.Provider.Extension` DSL. Within `provider do`, a Party kind uses `instances do`, `parties do`, and `places do` sections to declare the roles it plays. diff --git a/lib/diffo/provider/components/calculations/field_from_assignment.ex b/lib/diffo/provider/components/calculations/field_from_assignment.ex index f3d4d4c..05dd779 100644 --- a/lib/diffo/provider/components/calculations/field_from_assignment.ex +++ b/lib/diffo/provider/components/calculations/field_from_assignment.ex @@ -3,7 +3,34 @@ # SPDX-License-Identifier: MIT defmodule Diffo.Provider.Calculations.FieldFromAssignment do - @moduledoc false + @moduledoc """ + Reads a field directly from an `AssignmentRelationship` record. + + Filters `AssignmentRelationship` by `target_id = current.id` and returns the named + field from each matching record — no second hop to the source instance. This is the + right choice when you want a value that lives on the relationship itself (`:value`, + `:thing`, `:pool`, `:alias`) rather than on the assigning instance. + + Use `FieldViaAssignedRelationship` instead when you need a field from the source + instance (e.g. `:name`). + + ## Options + + - `field:` *(required)* — atom naming the field to read from the relationship record + (e.g. `:value`, `:thing`, `:pool`, `:alias`). + - `alias:` *(optional)* — atom matching the `alias` attribute on the relationship. + When omitted, all assignments where `target_id = current.id` are included. + + ## Examples + + # Port number assigned to this service under the :primary slot + calculate :assigned_port, {:array, :integer}, + {Diffo.Provider.Calculations.FieldFromAssignment, [alias: :primary, field: :value]} + + # Pool name for every assignment on this instance + calculate :assignment_pools, {:array, :atom}, + {Diffo.Provider.Calculations.FieldFromAssignment, [field: :pool]} + """ use Ash.Resource.Calculation @impl true diff --git a/lib/diffo/provider/components/calculations/field_via_assigned_relationship.ex b/lib/diffo/provider/components/calculations/field_via_assigned_relationship.ex index 6eab486..bc70678 100644 --- a/lib/diffo/provider/components/calculations/field_via_assigned_relationship.ex +++ b/lib/diffo/provider/components/calculations/field_via_assigned_relationship.ex @@ -3,7 +3,32 @@ # SPDX-License-Identifier: MIT defmodule Diffo.Provider.Calculations.FieldViaAssignedRelationship do - @moduledoc false + @moduledoc """ + Reads a field from the source instance of an `AssignmentRelationship`. + + Traverses `AssignmentRelationship` in reverse — filtering by `target_id = current.id` + — to reach the source instances (pool owners) that assigned something to this instance, + then returns the named field from each. + + ## Options + + - `field:` *(required)* — atom naming the field to read from the source instance + (e.g. `:name`, `:type`). + - `via:` *(optional)* — list of alias atoms to step through. Each step filters + `AssignmentRelationship` by the alias and follows `source_id` to the next set of + instances. Multi-hop is supported by chaining steps. When omitted, all assignments + where `target_id = current.id` are traversed without alias filtering. + + ## Examples + + # Name of the CVC that holds the :svlan assignment slot on this AVC + calculate :cvc_id, {:array, :string}, + {Diffo.Provider.Calculations.FieldViaAssignedRelationship, [via: [:svlan], field: :name]} + + # Name of every instance that has ever assigned anything to this one + calculate :assigner_names, {:array, :string}, + {Diffo.Provider.Calculations.FieldViaAssignedRelationship, [field: :name]} + """ use Ash.Resource.Calculation @impl true diff --git a/lib/diffo/provider/components/calculations/field_via_relationship.ex b/lib/diffo/provider/components/calculations/field_via_relationship.ex index 219965b..af5bf14 100644 --- a/lib/diffo/provider/components/calculations/field_via_relationship.ex +++ b/lib/diffo/provider/components/calculations/field_via_relationship.ex @@ -3,7 +3,39 @@ # SPDX-License-Identifier: MIT defmodule Diffo.Provider.Calculations.FieldViaRelationship do - @moduledoc false + @moduledoc """ + Reads a field from target instances reached via `DefinedSimpleRelationship`. + + Traverses `DefinedSimpleRelationship` in the forward direction — filtering by + `source_id = current.id` — and returns the named field from each resolved target + instance. Both `type:` and `alias:` are optional filters; when omitted they match + any value on that dimension. + + ## Options + + - `field:` *(required)* — atom naming the field to read from the target instance + (e.g. `:name`, `:type`). + - `alias:` *(optional)* — atom matching the `alias` attribute on the relationship. + When omitted, relationships with any alias (including nil) are included. + - `type:` *(optional)* — atom matching the `type` attribute on the relationship + (e.g. `:assignedTo`, `:reliesOn`). When omitted, all types are included. + + Providing neither filter returns fields from every forward `DefinedSimpleRelationship` + on this instance. In practice at least one of `alias:` or `type:` should be supplied, + since a source instance typically has many forward relationships pointing to unrelated + things. + + ## Examples + + # Name of the target reached via the :provides alias + calculate :provider_name, {:array, :string}, + {Diffo.Provider.Calculations.FieldViaRelationship, [alias: :provides, field: :name]} + + # Name of the target reached via the :link alias, restricted to :assignedTo type + calculate :assigned_linked_name, {:array, :string}, + {Diffo.Provider.Calculations.FieldViaRelationship, + [type: :assignedTo, alias: :link, field: :name]} + """ use Ash.Resource.Calculation @impl true diff --git a/lib/diffo/provider/components/calculations/inherited_party.ex b/lib/diffo/provider/components/calculations/inherited_party.ex index ab58489..7f1ccc3 100644 --- a/lib/diffo/provider/components/calculations/inherited_party.ex +++ b/lib/diffo/provider/components/calculations/inherited_party.ex @@ -3,7 +3,16 @@ # SPDX-License-Identifier: MIT defmodule Diffo.Provider.Calculations.InheritedParty do - @moduledoc false + @moduledoc """ + Backing calculation for `inherited_party` DSL declarations. + + Traverses `AssignmentRelationship` by alias to reach source instances, then reads + their `PartyRef` records for the declared `source_role`. Injected automatically by + `TransformInheritedRefs` — do not reference this module directly; use the + `inherited_party` DSL entity instead. + + See `Diffo.Provider.Extension.InheritedPartyDeclaration` for the DSL options. + """ use Ash.Resource.Calculation @impl true diff --git a/lib/diffo/provider/components/calculations/inherited_place.ex b/lib/diffo/provider/components/calculations/inherited_place.ex index cc252bd..b15a2b5 100644 --- a/lib/diffo/provider/components/calculations/inherited_place.ex +++ b/lib/diffo/provider/components/calculations/inherited_place.ex @@ -3,7 +3,16 @@ # SPDX-License-Identifier: MIT defmodule Diffo.Provider.Calculations.InheritedPlace do - @moduledoc false + @moduledoc """ + Backing calculation for `inherited_place` DSL declarations. + + Traverses `AssignmentRelationship` by alias to reach source instances, then reads + their `PlaceRef` records for the declared `source_role`. Injected automatically by + `TransformInheritedRefs` — do not reference this module directly; use the + `inherited_place` DSL entity instead. + + See `Diffo.Provider.Extension.InheritedPlaceDeclaration` for the DSL options. + """ use Ash.Resource.Calculation @impl true diff --git a/lib/diffo/provider/extension/inherited_party_declaration.ex b/lib/diffo/provider/extension/inherited_party_declaration.ex index d02437f..5427b3a 100644 --- a/lib/diffo/provider/extension/inherited_party_declaration.ex +++ b/lib/diffo/provider/extension/inherited_party_declaration.ex @@ -3,7 +3,32 @@ # SPDX-License-Identifier: MIT defmodule Diffo.Provider.Extension.InheritedPartyDeclaration do - @moduledoc "DSL entity declaring an inherited party role — derived by traversing the assignment graph" + @moduledoc """ + DSL entity for an `inherited_party` declaration inside `parties do` on an Instance resource. + + Generates an Ash calculation of the same name as `role` that traverses the assignment + graph to inherit a party from a related source instance. The calculation is injected + by `TransformInheritedRefs` at compile time — no `PartyRef` edge is created on the + consuming instance itself. + + ## Fields + + - `role` — atom; the name of the generated calculation (and the party slot name from + the consumer's perspective). + - `source_role` — atom; the `PartyRef` role to read from the resolved source instance + (e.g. `:provider`). Required. + - `via` — optional list of alias atoms for multi-hop traversal. When nil the role name + is used as the single alias step (single-hop default). When provided, each step + filters `AssignmentRelationship` by that alias atom before following `source_id` to + the next set of instances. + + ## Example + + parties do + inherited_party :provider, source_role: :provider + inherited_party :nni_owner, via: [:uplink], source_role: :owner + end + """ defstruct [:role, :via, :source_role, __spark_metadata__: nil] defimpl String.Chars do diff --git a/lib/diffo/provider/extension/inherited_place_declaration.ex b/lib/diffo/provider/extension/inherited_place_declaration.ex index 7f55dc9..147602d 100644 --- a/lib/diffo/provider/extension/inherited_place_declaration.ex +++ b/lib/diffo/provider/extension/inherited_place_declaration.ex @@ -3,7 +3,32 @@ # SPDX-License-Identifier: MIT defmodule Diffo.Provider.Extension.InheritedPlaceDeclaration do - @moduledoc "DSL entity declaring an inherited place role — derived by traversing the assignment graph" + @moduledoc """ + DSL entity for an `inherited_place` declaration inside `places do` on an Instance resource. + + Generates an Ash calculation of the same name as `role` that traverses the assignment + graph to inherit a place from a related source instance. The calculation is injected + by `TransformInheritedRefs` at compile time — no `PlaceRef` edge is created on the + consuming instance itself. + + ## Fields + + - `role` — atom; the name of the generated calculation (and the place slot name from + the consumer's perspective). + - `source_role` — atom; the `PlaceRef` role to read from the resolved source instance + (e.g. `:location`). Required. + - `via` — optional list of alias atoms for multi-hop traversal. When nil the role name + is used as the single alias step (single-hop default). When provided, each step + filters `AssignmentRelationship` by that alias atom before following `source_id` to + the next set of instances. + + ## Example + + places do + inherited_place :installation_site, source_role: :location + inherited_place :exchange, via: [:primary, :uplink], source_role: :location + end + """ defstruct [:role, :via, :source_role, __spark_metadata__: nil] defimpl String.Chars do diff --git a/usage-rules.md b/usage-rules.md index 6491e7c..218ae5c 100644 --- a/usage-rules.md +++ b/usage-rules.md @@ -549,6 +549,144 @@ defmodule MyApp.GeographicSite do end ``` +## Aliases on relationships + +Both `AssignmentRelationship` and `DefinedSimpleRelationship` carry an optional `:alias` +attribute — an atom given to a relationship slot by the consuming (target) side. + +An alias is the consumer's stable name for a slot before (or when) the relationship is +bound. It survives the relationship's lifetime unchanged. Think of it as a "baby name" +for a slot: the AVC says "I have a slot called `:svlan`"; when the CVC assigns a VLAN to +that AVC, the `AssignmentRelationship` record carries `alias: :svlan`. No matter which +CVC fills the slot or how many times the assignment is changed, the alias stays fixed. + +Identity constraints enforce uniqueness: +- `AssignmentRelationship` — `[:target_id, :alias]` — at most one assignment per + (target, alias) pair. This is how the consumer guarantees slot uniqueness. +- `DefinedSimpleRelationship` — `[:source_id, :alias]` — at most one outgoing + relationship per (source, alias) pair. + +Aliases are the join key for the first-order expectation system (issue #74): an +expectation declares an alias for a slot it expects to be filled; the actual relationship +carries the same alias, so intent and fulfilment can be matched precisely. Without the +expectation system in place, aliases appear to be optional metadata — with it, they are +the primary correlation key. + +```elixir +# Assigning with an alias — the AVC names its SVLAN slot :svlan +Servo.assign_port(cvc, %{ + assignment: %Assignment{ + assignee_id: avc.id, + operation: :auto_assign, + alias: :svlan + } +}) +``` + +## `inherited_place` and `inherited_party` DSL + +Declare `inherited_place` or `inherited_party` inside `places do` / `parties do` on an +Instance resource to generate an Ash calculation that traverses the assignment graph and +inherits a place or party from the source instance. + +No `PlaceRef` or `PartyRef` edge is created on the consuming instance — the calculation +IS the reference. The result is a list (consistent with all traversal calculations). + +```elixir +provider do + places do + # Single-hop: traverses AssignmentRelationship where alias = :installation_site, + # reads PlaceRef with role :location from the source instance + inherited_place :installation_site, source_role: :location + + # Explicit alias (same as above written long-form) + inherited_place :exchange, via: [:exchange], source_role: :location + + # Multi-hop: :primary slot on this instance → :uplink slot on that instance → + # reads :location PlaceRef from the final source + inherited_place :exchange, via: [:primary, :uplink], source_role: :location + end + + parties do + inherited_party :provider, source_role: :provider + end +end +``` + +Options: +- `source_role:` *(required)* — the `PlaceRef`/`PartyRef` role to read from the resolved + source instance. +- `via:` *(optional)* — explicit list of alias atoms for multi-hop traversal. When + omitted, the role name itself is used as the single alias step. + +The DSL entity must be declared in the correct section (`places do` for `inherited_place`, +`parties do` for `inherited_party`). The generated calculation name matches the declared role. + +## Field calculation modules + +Three general-purpose calculation modules cover reading fields across the assignment and +relationship graph. Declare them in a `calculations do` block on any Instance resource. + +### `FieldFromAssignment` + +Reads a field directly from an `AssignmentRelationship` record — no hop to the source +instance. Use this when you want a value that lives on the relationship itself. + +```elixir +# Port number assigned to this service under the :svlan slot +calculate :assigned_vlan, {:array, :integer}, + {Diffo.Provider.Calculations.FieldFromAssignment, [alias: :svlan, field: :value]} + +# Pool name for every assignment on this instance (no alias filter) +calculate :assignment_pools, {:array, :atom}, + {Diffo.Provider.Calculations.FieldFromAssignment, [field: :pool]} +``` + +Options: `field:` (required), `alias:` (optional). + +### `FieldViaAssignedRelationship` + +Traverses `AssignmentRelationship` in reverse (target → source) and reads a field from +each source instance. Use this when you want a field that belongs to the assigning +instance, not the relationship record. + +```elixir +# Name of the CVC holding the :svlan assignment slot on this AVC +calculate :cvc_id, {:array, :string}, + {Diffo.Provider.Calculations.FieldViaAssignedRelationship, [via: [:svlan], field: :name]} +``` + +Options: `field:` (required), `via:` (optional list of alias steps — omit for unaliased). + +### `FieldViaRelationship` + +Traverses `DefinedSimpleRelationship` in the forward direction (source → target) filtered +by `alias:` and/or `type:`, and reads a field from each target instance. + +```elixir +# Name of the target reached via the :provides alias +calculate :downstream_name, {:array, :string}, + {Diffo.Provider.Calculations.FieldViaRelationship, [alias: :provides, field: :name]} + +# Name narrowed by both type and alias +calculate :assigned_node_name, {:array, :string}, + {Diffo.Provider.Calculations.FieldViaRelationship, + [type: :assignedTo, alias: :node, field: :name]} +``` + +Options: `field:` (required), `alias:` (optional), `type:` (optional). Provide at least +one of `alias:` or `type:` — querying by `source_id` alone returns all forward +relationships mixed together, which is rarely useful. + +### Choosing between the three + +| I want… | Use | +|---------|-----| +| A value stored on the assignment record itself (`:value`, `:pool`, `:alias`) | `FieldFromAssignment` | +| A field from the instance that assigned something to me | `FieldViaAssignedRelationship` | +| A field from the instance I have a forward relationship to | `FieldViaRelationship` | +| A place/party inherited from the assigning instance | `inherited_place` / `inherited_party` DSL | + ## Common mistakes - **Do not add your resources to `Diffo.Provider`** — that domain is closed. Build your own