Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions .gitignore.license

This file was deleted.

3 changes: 0 additions & 3 deletions .tool-versions.license

This file was deleted.

12 changes: 12 additions & 0 deletions REUSE.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
version = 1

[[annotations]]
path = [
".gitignore",
".tool-versions",
"mix.lock",
"logos/diffo.jpg",
"documentation/dsls/**",
]
SPDX-FileCopyrightText = "2025 diffo contributors <https://github.com/diffo-dev/diffo/graphs.contributors>"
SPDX-License-Identifier = "MIT"
58 changes: 46 additions & 12 deletions diffo.livemd
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<!--
<!--
SPDX-FileCopyrightText: 2025 diffo contributors <https://github.com/diffo-dev/diffo/graphs.contributors>

SPDX-License-Identifier: MIT
Expand All @@ -9,7 +9,7 @@ SPDX-License-Identifier: MIT
```elixir
Mix.install(
[
{:diffo, "~> 0.2.0"}
{:diffo, path: "/Users/Beanlanda/git/diffo"}
],
consolidate_protocols: false
)
Expand Down Expand Up @@ -113,10 +113,41 @@ alias Diffo.Provider.PartyRef
alias Diffo.Provider.Place
alias Diffo.Provider.PartyRef
alias Diffo.Uuid
alias Diffo.Type.Value
import Jason, only: [encode: 2]
use Outstand
```

The value types used in this livebook are defined without the Provider Extension DSL — each requires a dedicated `Ash.TypedStruct` so that `Diffo.Type.Dynamic` can round-trip values through Neo4j storage.

```elixir
defmodule Diffo.Livebook.E2eValue do
@derive Jason.Encoder
use Ash.TypedStruct
typed_struct do
field :downstream, :integer
field :upstream, :integer
field :units, :atom
end
end

defmodule Diffo.Livebook.OptionsValue do
@derive Jason.Encoder
use Ash.TypedStruct
typed_struct do
field :options, {:array, :atom}
end
end

defmodule Diffo.Livebook.TechnologyValue do
@derive Jason.Encoder
use Ash.TypedStruct
typed_struct do
field :access, :atom
end
end
```

We can either create specification instances with Ash directly, or use the Diffo.Provider code interface.

```elixir
Expand Down Expand Up @@ -173,13 +204,13 @@ individual =
Diffo.Provider.create_party!(%{
id: "IND000000897354",
name: :individualId,
referredType: :Individual
referred_type: :Individual
})
org =
Diffo.Provider.create_party!(%{
id: "ORG000000123456",
name: :organizationId,
referredType: :Organization
referred_type: :Organization
})
parties = [individual, org]
Jason.encode!(parties, pretty: true) |> IO.puts
Expand All @@ -197,7 +228,7 @@ Jason.encode!(parties, pretty: true) |> IO.puts
We'll also add a CustomerSite place where the service is to be delivered to the customer. Historically this is the Z-end.

```elixir
z_end = Provider.create_place!(%{id: "1657363", name: :addressId, href: "place/telco/1657363", referredType: :GeographicAddress})
z_end = Provider.create_place!(%{id: "1657363", name: :addressId, href: "place/telco/1657363", referred_type: :GeographicAddress})
Jason.encode!(z_end, pretty: true) |> IO.puts
```

Expand Down Expand Up @@ -230,7 +261,7 @@ First we'll create a backup feature to indicate that we want mobile backup, and
We'll create an e2e 'instance' characteristic to detail our bandwidth and latency requirements:

```elixir
e2e = Provider.create_characteristic!(%{type: :instance, name: :e2e, value: %{bandwidth: %{downstream: 250, upstream: 25, units: :Mbps}}})
e2e = Provider.create_characteristic!(%{type: :instance, name: :e2e, value: Value.dynamic(%Diffo.Livebook.E2eValue{downstream: 250, upstream: 25, units: :Mbps})})
broadband_0001 = broadband_0001
|> Provider.relate_instance_characteristics!(%{characteristics: [e2e.id]})
broadband_0001 |> Jason.encode!(pretty: true) |> IO.puts
Expand All @@ -240,7 +271,7 @@ We'll create mobile backup and device management features. The device management

```elixir
backup = Provider.create_feature!(%{name: :backup, isEnabled: true})
options = Provider.create_characteristic!(%{type: :feature, name: :options, value: [:updates, :monitoring]})
options = Provider.create_characteristic!(%{type: :feature, name: :options, value: Value.dynamic(%Diffo.Livebook.OptionsValue{options: [:updates, :monitoring]})})
device_management = Provider.create_feature!(%{name: :deviceManagement, isEnabled: true, characteristics: [options.id]})
broadband_0001 = broadband_0001
|> Provider.relate_instance_features!(%{features: [backup.id, device_management.id]})
Expand Down Expand Up @@ -305,13 +336,13 @@ We'll have a few things outstanding which we would normally find out during serv
We recommend using outstanding to drive next task logic, so that the orchestration is directed by the difference engine. This could look like an address lookup (where we learn the provider) followed by a provider service qualification (where we learn the technology) and the related network places.

```elixir
nbn = Provider.create_party!(%{id: :nbn, name: "NBNCo", referredType: :Organization})
nbn = Provider.create_party!(%{id: "nbn", name: "NBNCo", referred_type: :Organization})
nbn_party_ref = Provider.create_party_ref!(%{instance_id: broadband_0001.id, party_id: nbn.id, role: :Provider})
provider_z_end = Provider.create_place!(%{id: "LOC000000899353", name: "locationId", href: "place/nbnco/LOC000000899353",referredType: :GeographicAddress})
provider_z_end = Provider.create_place!(%{id: "LOC000000899353", name: "locationId", href: "place/nbnco/LOC000000899353", referred_type: :GeographicAddress})
provider_z_end_place_ref = Provider.create_place_ref!(%{instance_id: broadband_0001.id, role: :CustomerSite, place_id: provider_z_end.id})
csa = Provider.create_place!(%{id: "CSA200000000685", name: "csaId", href: "place/nbnco/CSA200000000685", referredType: :GeographicLocation})
csa = Provider.create_place!(%{id: "CSA200000000685", name: "csaId", href: "place/nbnco/CSA200000000685", referred_type: :GeographicLocation})
csa_place_ref = Provider.create_place_ref!(%{instance_id: broadband_0001.id, role: :ServingArea, place_id: csa.id})
poi = Provider.create_place!(%{id: "2CAR", name: "poiId", href: "place/nbnco/2CAR",referredType: :GeographicSite})
poi = Provider.create_place!(%{id: "2CAR", name: "poiId", href: "place/nbnco/2CAR", referred_type: :GeographicSite})
poi_place_ref = Provider.create_place_ref!(%{instance_id: broadband_0001.id, role: :AccessNNI, place_id: poi.id})

places = Diffo.Provider.list_place_refs!()
Expand All @@ -331,7 +362,7 @@ outstanding = expected_instance --- broadband_0001
We create and add the technology characteristic to the actual service. This should resolve the characteristic expectation.

```elixir
technology = Provider.create_characteristic!(%{type: :instance, name: :technology, value: %{access: :nbnEthernet}})
technology = Provider.create_characteristic!(%{type: :instance, name: :technology, value: Value.dynamic(%Diffo.Livebook.TechnologyValue{access: :nbnEthernet})})
broadband_0001 |> Provider.relate_instance_characteristics!(%{characteristics: [technology.id]})


Expand All @@ -358,6 +389,7 @@ Given that our feasibilityCheck above was complete we want to set the :feasibili

```elixir
broadband_0001 = broadband_0001 |> Provider.feasibilityCheck_service!(%{service_operating_status: :feasible})
broadband_0001 = Provider.get_instance_by_id!(broadband_0001.id)
broadband_0001 |> Jason.encode!(pretty: true) |> IO.puts
```

Expand Down Expand Up @@ -450,4 +482,6 @@ broadband_0001 |> Jason.encode!(pretty: true) |> IO.puts
In this tutorial you've used Diffo to create, relate and update some TMF Service and Resources, simulating activities over the service and resource lifecycle, and you've learned how this functionality is underpinned by open source Neo4j, Ash Framework and Elixir.
Diffo contains an example Access domain showing how to specialise the structure and behaviour of TMF Services and Resources.

But there is a lot of friction here and we still only have structure and not much behaviour. In practice, you'd use the Provider Extension DSLs to do the heavy lifting. But what you've learned here is foundational to help you move on to the other livebooks.

If you find Diffo useful please visit and star on [github](https://github.com/diffo-dev/diffo/). Feel free to join discussions and raise issues to discuss PR's.
65 changes: 52 additions & 13 deletions documentation/dsls/DSL-Diffo.Provider.Instance.Extension.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,14 @@ This file was generated by Spark. Do not edit it by hand.
-->
# Diffo.Provider.Instance.Extension

DSL Extension customising an Instance
DSL Extension customising an Instance.

Provides compile-time declaration blocks for domain-specific Service and Resource kinds
built on `Diffo.Provider.BaseInstance`. All declarations are introspectable via
`Diffo.Provider.Instance.Extension.Info`.

See the [DSL cheat sheet](DSL-Diffo.Provider.Instance.Extension.html) for the full DSL reference.
See `Diffo.Provider.BaseInstance` for full usage documentation.


## specification
Expand Down Expand Up @@ -117,16 +124,10 @@ Adds a Characteristic



### Introspection

Target: `Diffo.Provider.Instance.Characteristic`




### Introspection

Target: `Diffo.Provider.Instance.Feature`



Expand Down Expand Up @@ -176,9 +177,6 @@ Adds a Characteristic



### Introspection

Target: `Diffo.Provider.Instance.Characteristic`



Expand All @@ -188,13 +186,15 @@ List of Instance Party roles

### Nested DSLs
* [party](#parties-party)
* [parties](#parties-parties)


### Examples
```
parties do
party :facilitated_by, MyApp.Rsp
party :overseen_by, MyApp.Person
party :provider, MyApp.Provider, calculate: :provider_calculation
parties :technician, MyApp.Technician, constraints: [min: 1, max: 3]
party :owner, MyApp.InfrastructureCo, reference: true
end

```
Expand All @@ -208,7 +208,7 @@ party role, party_type
```


Declares a party role on this Instance
Declares a singular party role on this Instance



Expand All @@ -220,7 +220,46 @@ Declares a party role on this Instance
|------|------|---------|------|
| [`role`](#parties-party-role){: #parties-party-role .spark-required} | `atom` | | The role name, an atom |
| [`party_type`](#parties-party-party_type){: #parties-party-party_type } | `any` | | The module of the Party kind. An atom module name such as a BaseParty-derived resource. |
### Options

| Name | Type | Default | Docs |
|------|------|---------|------|
| [`reference`](#parties-party-reference){: #parties-party-reference } | `boolean` | `false` | If true, no direct PartyRef edge is created; the party is reachable by graph traversal. |
| [`calculate`](#parties-party-calculate){: #parties-party-calculate } | `atom` | | Name of an Ash calculation on this resource that produces the party at build time. |





### Introspection

Target: `Diffo.Provider.Instance.Extension.PartyDeclaration`

### parties.parties
```elixir
parties role, party_type
```


Declares a plural party role on this Instance





### Arguments

| Name | Type | Default | Docs |
|------|------|---------|------|
| [`role`](#parties-parties-role){: #parties-parties-role .spark-required} | `atom` | | The role name, an atom |
| [`party_type`](#parties-parties-party_type){: #parties-parties-party_type } | `any` | | The module of the Party kind. An atom module name such as a BaseParty-derived resource. |
### Options

| Name | Type | Default | Docs |
|------|------|---------|------|
| [`reference`](#parties-parties-reference){: #parties-parties-reference } | `boolean` | `false` | If true, no direct PartyRef edge is created; the party is reachable by graph traversal. |
| [`calculate`](#parties-parties-calculate){: #parties-parties-calculate } | `atom` | | Name of an Ash calculation on this resource that produces the party at build time. |
| [`constraints`](#parties-parties-constraints){: #parties-parties-constraints } | `keyword` | | Multiplicity constraints on the number of parties in this role, e.g. [min: 1, max: 3] |



Expand Down

This file was deleted.

32 changes: 19 additions & 13 deletions documentation/dsls/DSL-Diffo.Provider.Party.Extension.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,25 @@ This file was generated by Spark. Do not edit it by hand.
-->
# Diffo.Provider.Party.Extension

DSL Extension customising a Party
DSL Extension customising a Party.

Provides compile-time declaration blocks for domain-specific Party kinds
built on `Diffo.Provider.BaseParty`. All declarations are introspectable via
`Diffo.Provider.Party.Extension.Info`.

## instance
See the [DSL cheat sheet](DSL-Diffo.Provider.Party.Extension.html) for the full DSL reference.


## instances
Declares the roles this Party kind plays with respect to Instances

### Nested DSLs
* [role](#instance-role)
* [role](#instances-role)


### Examples
```
instance do
instances do
role :facilitates, MyApp.AccessService
end

Expand All @@ -24,7 +30,7 @@ end



### instance.role
### instances.role
```elixir
role role, party_type
```
Expand All @@ -40,8 +46,8 @@ Declares a role this Party kind plays

| Name | Type | Default | Docs |
|------|------|---------|------|
| [`role`](#instance-role-role){: #instance-role-role .spark-required} | `atom` | | The role name, an atom |
| [`party_type`](#instance-role-party_type){: #instance-role-party_type } | `any` | | The module of the related resource |
| [`role`](#instances-role-role){: #instances-role-role .spark-required} | `atom` | | The role name, an atom |
| [`party_type`](#instances-role-party_type){: #instances-role-party_type } | `any` | | The module of the related resource |



Expand All @@ -55,16 +61,16 @@ Target: `Diffo.Provider.Party.Extension.InstanceRole`



## party
## parties
Declares the roles this Party kind plays with respect to other Parties

### Nested DSLs
* [role](#party-role)
* [role](#parties-role)


### Examples
```
party do
parties do
role :managed_by, MyApp.Person
end

Expand All @@ -73,7 +79,7 @@ end



### party.role
### parties.role
```elixir
role role, party_type
```
Expand All @@ -89,8 +95,8 @@ Declares a role this Party kind plays with respect to other Parties

| Name | Type | Default | Docs |
|------|------|---------|------|
| [`role`](#party-role-role){: #party-role-role .spark-required} | `atom` | | The role name, an atom |
| [`party_type`](#party-role-party_type){: #party-role-party_type } | `any` | | The module of the related Party kind |
| [`role`](#parties-role-role){: #parties-role-role .spark-required} | `atom` | | The role name, an atom |
| [`party_type`](#parties-role-party_type){: #parties-role-party_type } | `any` | | The module of the related Party kind |



Expand Down

This file was deleted.

Loading
Loading