diff --git a/.formatter.exs b/.formatter.exs index 1a29889..618aa09 100644 --- a/.formatter.exs +++ b/.formatter.exs @@ -3,30 +3,21 @@ # SPDX-License-Identifier: MIT # Used by "mix format" -locals_without_parens = [ - id: 1, - category: 1, - is_enabled?: 1, - characteristic: 2, - pick: 1, - rename: 1, - field: 3, - expect: 1, - relate: 1, - translate: 1, - guard: 1, - customize: 1, - order: 1, - initial_states: 1, - default_initial_state: 1, - state_attribute: 1, - transition: 1 -] +locals_without_parens = [] [ plugins: [Spark.Formatter], inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"], - import_deps: [:ash], + import_deps: [ + :diffo, + :ash, + :ash_state_machine, + :ash_neo4j, + :ash_jason, + :ash_outstanding, + :ash_json_api, + :plug + ], locals_without_parens: locals_without_parens, export: [ locals_without_parens: locals_without_parens diff --git a/.gitignore b/.gitignore index 9481f4e..6d6651e 100644 --- a/.gitignore +++ b/.gitignore @@ -27,4 +27,5 @@ diffo-*.tar .DS_Store # Agent related -.claude/* \ No newline at end of file +.claude/* +CLAUDE* \ No newline at end of file diff --git a/.igniter.exs b/.igniter.exs new file mode 100644 index 0000000..b84da46 --- /dev/null +++ b/.igniter.exs @@ -0,0 +1,14 @@ +# SPDX-FileCopyrightText: 2025 diffo_example contributors +# +# SPDX-License-Identifier: MIT + +# This is a configuration file for igniter. +# For option documentation, see https://hexdocs.pm/igniter/Igniter.Project.IgniterConfig.html +# To keep it up to date, use `mix igniter.setup` +[ + module_location: :outside_matching_folder, + extensions: [], + deps_location: :last_list_literal, + source_folders: ["lib", "test/support"], + dont_move_files: [~r"lib/mix"] +] diff --git a/config/runtime.exs b/config/runtime.exs index 46ce236..76d01eb 100644 --- a/config/runtime.exs +++ b/config/runtime.exs @@ -4,6 +4,12 @@ import Config +config :bolty, Bolt, + uri: "bolt://localhost:7687", + auth: [username: "neo4j", password: "password"], + pool_size: 10, + name: Bolt + if config_env() == :prod do database_url = System.get_env("DATABASE_URL") || diff --git a/lib/access/resources/characteristic_values/cable_value.ex b/lib/access/resources/characteristic_values/cable_value.ex index e5a315a..1cb7cf5 100644 --- a/lib/access/resources/characteristic_values/cable_value.ex +++ b/lib/access/resources/characteristic_values/cable_value.ex @@ -12,7 +12,7 @@ defmodule DiffoExample.Access.CableValue do jason do pick [:name, :pairs, :length, :loss, :technology] - compact(true) + compact true end outstanding do diff --git a/lib/access/resources/characteristic_values/card_value.ex b/lib/access/resources/characteristic_values/card_value.ex index 44441da..27cd4ba 100644 --- a/lib/access/resources/characteristic_values/card_value.ex +++ b/lib/access/resources/characteristic_values/card_value.ex @@ -12,7 +12,7 @@ defmodule DiffoExample.Access.CardValue do jason do pick [:name, :family, :model, :technology] - compact(true) + compact true end outstanding do diff --git a/lib/access/resources/characteristic_values/float_unit.ex b/lib/access/resources/characteristic_values/float_unit.ex index ad05cdf..4878eef 100644 --- a/lib/access/resources/characteristic_values/float_unit.ex +++ b/lib/access/resources/characteristic_values/float_unit.ex @@ -12,7 +12,7 @@ defmodule DiffoExample.Access.FloatUnit do jason do pick [:amount, :unit] - compact(true) + compact true end outstanding do diff --git a/lib/access/resources/characteristic_values/integer_unit.ex b/lib/access/resources/characteristic_values/integer_unit.ex index 11d4c9b..99d1e25 100644 --- a/lib/access/resources/characteristic_values/integer_unit.ex +++ b/lib/access/resources/characteristic_values/integer_unit.ex @@ -12,7 +12,7 @@ defmodule DiffoExample.Access.IntegerUnit do jason do pick [:amount, :unit] - compact(true) + compact true end outstanding do diff --git a/lib/access/resources/characteristic_values/path_value.ex b/lib/access/resources/characteristic_values/path_value.ex index 7ea2e0e..6028e41 100644 --- a/lib/access/resources/characteristic_values/path_value.ex +++ b/lib/access/resources/characteristic_values/path_value.ex @@ -12,7 +12,7 @@ defmodule DiffoExample.Access.PathValue do jason do pick [:name, :sections, :length, :loss, :technology] - compact(true) + compact true end outstanding do diff --git a/lib/access/resources/characteristic_values/shelf_value.ex b/lib/access/resources/characteristic_values/shelf_value.ex index 80b1313..ec7fa53 100644 --- a/lib/access/resources/characteristic_values/shelf_value.ex +++ b/lib/access/resources/characteristic_values/shelf_value.ex @@ -12,7 +12,7 @@ defmodule DiffoExample.Access.ShelfValue do jason do pick [:name, :family, :model, :technology] - compact(true) + compact true end outstanding do diff --git a/lib/access/services/characteristic_values/aggregate_interface.ex b/lib/access/services/characteristic_values/aggregate_interface.ex index 40d677c..109f966 100644 --- a/lib/access/services/characteristic_values/aggregate_interface.ex +++ b/lib/access/services/characteristic_values/aggregate_interface.ex @@ -12,7 +12,7 @@ defmodule DiffoExample.Access.AggregateInterface do jason do pick [:name, :physical_interface, :physical_layer, :link_layer, :svlan_id, :vpi] - compact(true) + compact true rename physical_interface: "physicalInterface", physical_layer: "physicalLayer", diff --git a/lib/access/services/characteristic_values/bandwidth_profile.ex b/lib/access/services/characteristic_values/bandwidth_profile.ex index bd1c35b..3e354c3 100644 --- a/lib/access/services/characteristic_values/bandwidth_profile.ex +++ b/lib/access/services/characteristic_values/bandwidth_profile.ex @@ -12,7 +12,7 @@ defmodule DiffoExample.Access.BandwidthProfile do jason do pick [:downstream, :upstream, :units] - compact(true) + compact true end outstanding do diff --git a/lib/access/services/characteristic_values/circuit.ex b/lib/access/services/characteristic_values/circuit.ex index 2aaddd4..c6a2fde 100644 --- a/lib/access/services/characteristic_values/circuit.ex +++ b/lib/access/services/characteristic_values/circuit.ex @@ -14,7 +14,7 @@ defmodule DiffoExample.Access.Circuit do jason do pick [:circuit_id, :cvlan_id, :vci, :encapsulation, :bandwidth_profile] - compact(true) + compact true rename circuit_id: "circuitId", vci: "VCI", bandwidth_profile: "bandwidthProfile" end diff --git a/lib/access/services/characteristic_values/constraints.ex b/lib/access/services/characteristic_values/constraints.ex index 720e45f..8987e34 100644 --- a/lib/access/services/characteristic_values/constraints.ex +++ b/lib/access/services/characteristic_values/constraints.ex @@ -12,7 +12,7 @@ defmodule DiffoExample.Access.Constraints do jason do pick [:max_latency, :min_profile] - compact(true) + compact true rename max_latency: "maxLatency", min_profile: "minProfile" end diff --git a/lib/access/services/characteristic_values/dslam.ex b/lib/access/services/characteristic_values/dslam.ex index 8042f43..3186679 100644 --- a/lib/access/services/characteristic_values/dslam.ex +++ b/lib/access/services/characteristic_values/dslam.ex @@ -12,7 +12,7 @@ defmodule DiffoExample.Access.Dslam do jason do pick [:name, :family, :model, :technology] - compact(true) + compact true end outstanding do diff --git a/lib/access/services/characteristic_values/line.ex b/lib/access/services/characteristic_values/line.ex index dbc5624..2741b3b 100644 --- a/lib/access/services/characteristic_values/line.ex +++ b/lib/access/services/characteristic_values/line.ex @@ -12,7 +12,7 @@ defmodule DiffoExample.Access.Line do jason do pick [:port, :slot, :standard, :profile] - compact(true) + compact true end outstanding do diff --git a/lib/nbn/initializer.ex b/lib/nbn/initializer.ex index 87e92e5..e11b615 100644 --- a/lib/nbn/initializer.ex +++ b/lib/nbn/initializer.ex @@ -14,12 +14,12 @@ defmodule DiffoExample.Nbn.Initializer do @rsps [ %{name: "Wedge-tail Telecom", short_name: :wedgetail, id: "0001"}, - %{name: "Quokka Connect", short_name: :quokka, id: "0002"}, - %{name: "Ibis Telecom", short_name: :ibis, id: "0003"}, - %{name: "Taipan Group", short_name: :taipan, id: "0004"}, - %{name: "Echidna Networks", short_name: :echidna, id: "0005"}, - %{name: "Dugong Digital", short_name: :dugong, id: "0006"}, - #%{name: "Lyrebird", short_name: :lyrebird, id: "0007"} + %{name: "Quokka Connect", short_name: :quokka, id: "0002"}, + %{name: "Ibis Telecom", short_name: :ibis, id: "0003"}, + %{name: "Taipan Group", short_name: :taipan, id: "0004"}, + %{name: "Echidna Networks", short_name: :echidna, id: "0005"}, + %{name: "Dugong Digital", short_name: :dugong, id: "0006"} + # %{name: "Lyrebird", short_name: :lyrebird, id: "0007"} ] def init do @@ -35,7 +35,9 @@ defmodule DiffoExample.Nbn.Initializer do {:error, _} -> seed_rsp(attrs) end rescue - e -> require Logger; Logger.error("Exception seeding RSP #{attrs.id}: #{inspect(e)}") + e -> + require Logger + Logger.error("Exception seeding RSP #{attrs.id}: #{inspect(e)}") end end) end diff --git a/lib/nbn/nbn.ex b/lib/nbn/nbn.ex index f377ff0..90ff05f 100644 --- a/lib/nbn/nbn.ex +++ b/lib/nbn/nbn.ex @@ -101,7 +101,6 @@ defmodule DiffoExample.Nbn do base_route "/rsp", Rsp do get :read end - end end @@ -171,6 +170,5 @@ defmodule DiffoExample.Nbn do define :suspend_rsp, action: :suspend define :deactivate_rsp, action: :deactivate end - end end diff --git a/lib/nbn/resources/avc.ex b/lib/nbn/resources/avc.ex index 294a951..ea20b16 100644 --- a/lib/nbn/resources/avc.ex +++ b/lib/nbn/resources/avc.ex @@ -24,15 +24,15 @@ defmodule DiffoExample.Nbn.Avc do extensions: [AshJsonApi.Resource], authorizers: [Ash.Policy.Authorizer] - json_api do - type "avc" - end - resource do description "An Ash Resource representing an Access Virtual Circuit (AVC)" plural_name :Avcs end + json_api do + type "avc" + end + structure do specification do id "b2c3d4e5-6f7a-4b8c-9d0e-1f2a3b4c5d6e" @@ -54,14 +54,6 @@ defmodule DiffoExample.Nbn.Avc do end end - attributes do - attribute :rsp_id, :string do - description "the owning RSP's id — nil for Perentie-managed infrastructure" - allow_nil? true - public? true - end - end - actions do create :build do description "creates a new AVC resource instance" @@ -115,13 +107,21 @@ defmodule DiffoExample.Nbn.Avc do end end + attributes do + attribute :rsp_id, :string do + description "the owning RSP's id — nil for Perentie-managed infrastructure" + allow_nil? true + public? true + end + end + def identifier() do DiffoExample.Nbn.Util.identifier("AVC") end # mines related resource to characteristics def mine_related(changeset, _context) when is_struct(changeset, Ash.Changeset) do - avc = Ash.load!(changeset.data, [reverse_relationships: [:characteristics]]) + avc = Ash.load!(changeset.data, reverse_relationships: [:characteristics]) cvlan = {:cvlan, Diffo.Unwrap.unwrap(hd(hd(avc.reverse_relationships).characteristics).value)} diff --git a/lib/nbn/resources/characteristic_values/avc_value.ex b/lib/nbn/resources/characteristic_values/avc_value.ex index e298968..8299e89 100644 --- a/lib/nbn/resources/characteristic_values/avc_value.ex +++ b/lib/nbn/resources/characteristic_values/avc_value.ex @@ -14,7 +14,7 @@ defmodule DiffoExample.Nbn.AvcValue do jason do pick [:cvlan, :bandwidth_profile] - compact(true) + compact true end outstanding do diff --git a/lib/nbn/resources/characteristic_values/cvc_value.ex b/lib/nbn/resources/characteristic_values/cvc_value.ex index c67f791..513e7ae 100644 --- a/lib/nbn/resources/characteristic_values/cvc_value.ex +++ b/lib/nbn/resources/characteristic_values/cvc_value.ex @@ -12,7 +12,7 @@ defmodule DiffoExample.Nbn.CvcValue do jason do pick [:svlan, :bandwidth] - compact(true) + compact true end outstanding do diff --git a/lib/nbn/resources/characteristic_values/nni_group_value.ex b/lib/nbn/resources/characteristic_values/nni_group_value.ex index 9b1b72c..104be47 100644 --- a/lib/nbn/resources/characteristic_values/nni_group_value.ex +++ b/lib/nbn/resources/characteristic_values/nni_group_value.ex @@ -12,7 +12,7 @@ defmodule DiffoExample.Nbn.NniGroupValue do jason do pick [:name, :location] - compact(true) + compact true end outstanding do diff --git a/lib/nbn/resources/characteristic_values/nni_value.ex b/lib/nbn/resources/characteristic_values/nni_value.ex index a4d69b3..5fdbaba 100644 --- a/lib/nbn/resources/characteristic_values/nni_value.ex +++ b/lib/nbn/resources/characteristic_values/nni_value.ex @@ -12,7 +12,7 @@ defmodule DiffoExample.Nbn.NniValue do jason do pick [:port_id, :capacity, :technology] - compact(true) + compact true rename port_id: "portId" end diff --git a/lib/nbn/resources/characteristic_values/ntd_value.ex b/lib/nbn/resources/characteristic_values/ntd_value.ex index 03f0be7..2b957ba 100644 --- a/lib/nbn/resources/characteristic_values/ntd_value.ex +++ b/lib/nbn/resources/characteristic_values/ntd_value.ex @@ -14,7 +14,7 @@ defmodule DiffoExample.Nbn.NtdValue do jason do pick [:model, :serial_number, :technology] - compact(true) + compact true end outstanding do diff --git a/lib/nbn/resources/characteristic_values/pri_value.ex b/lib/nbn/resources/characteristic_values/pri_value.ex index a678aeb..2c71e9e 100644 --- a/lib/nbn/resources/characteristic_values/pri_value.ex +++ b/lib/nbn/resources/characteristic_values/pri_value.ex @@ -16,7 +16,7 @@ defmodule DiffoExample.Nbn.PriValue do jason do pick [:avcid, :uniid, :technology, :bandwidth_profile, :speeds] - compact(true) + compact true rename avcid: "AVCID", uniid: "UNIID", bandwidth_profile: "bandwidthProfile" end diff --git a/lib/nbn/resources/characteristic_values/uni_value.ex b/lib/nbn/resources/characteristic_values/uni_value.ex index fcdd6a3..b3689e4 100644 --- a/lib/nbn/resources/characteristic_values/uni_value.ex +++ b/lib/nbn/resources/characteristic_values/uni_value.ex @@ -14,7 +14,7 @@ defmodule DiffoExample.Nbn.UniValue do jason do pick [:port, :encapsulation, :technology] - compact(true) + compact true end outstanding do diff --git a/lib/nbn/resources/cvc.ex b/lib/nbn/resources/cvc.ex index 30f64de..32e6b0a 100644 --- a/lib/nbn/resources/cvc.ex +++ b/lib/nbn/resources/cvc.ex @@ -26,21 +26,23 @@ defmodule DiffoExample.Nbn.Cvc do extensions: [AshJsonApi.Resource], authorizers: [Ash.Policy.Authorizer] - json_api do - type "cvc" - end - resource do description "An Ash Resource representing a Connectivity Virtual Circuit (CVC)" plural_name :Cvcs end + json_api do + type "cvc" + end + structure do specification do id "d4e5f6a7-8b9c-4d0e-bf1a-3b4c5d6e7f8a" name "cvc" type :resourceSpecification + description "A Connectivity Virtual Circuit Resource Instance that aggregates AVCs and terminates at an NNI Group" + category "Network Resource" end @@ -56,14 +58,6 @@ defmodule DiffoExample.Nbn.Cvc do end end - attributes do - attribute :rsp_id, :string do - description "the owning RSP's id — nil for Perentie-managed infrastructure" - allow_nil? true - public? true - end - end - actions do create :build do description "creates a new CVC resource instance" @@ -128,6 +122,14 @@ defmodule DiffoExample.Nbn.Cvc do end end + attributes do + attribute :rsp_id, :string do + description "the owning RSP's id — nil for Perentie-managed infrastructure" + allow_nil? true + public? true + end + end + def identifier() do DiffoExample.Nbn.Util.identifier("CVC") end diff --git a/lib/nbn/resources/nbn_ethernet.ex b/lib/nbn/resources/nbn_ethernet.ex index 1d15ba4..3eb2007 100644 --- a/lib/nbn/resources/nbn_ethernet.ex +++ b/lib/nbn/resources/nbn_ethernet.ex @@ -25,15 +25,15 @@ defmodule DiffoExample.Nbn.NbnEthernet do extensions: [AshJsonApi.Resource], authorizers: [Ash.Policy.Authorizer] - json_api do - type "nbnEthernet" - end - resource do description "An Ash Resource representing an NBN Ethernet access" plural_name :NbnEthernets end + json_api do + type "nbnEthernet" + end + structure do specification do id "f2a4c6e8-1b3d-4f5a-8c7e-9d0b2e4f6a8c" @@ -54,14 +54,6 @@ defmodule DiffoExample.Nbn.NbnEthernet do end end - attributes do - attribute :rsp_id, :string do - description "the owning RSP's id — nil for Perentie-managed infrastructure" - allow_nil? true - public? true - end - end - actions do create :build do description "creates a new NBN Ethernet access resource instance" @@ -115,6 +107,14 @@ defmodule DiffoExample.Nbn.NbnEthernet do end end + attributes do + attribute :rsp_id, :string do + description "the owning RSP's id — nil for Perentie-managed infrastructure" + allow_nil? true + public? true + end + end + def identifier() do DiffoExample.Nbn.Util.identifier("PRI") end diff --git a/lib/nbn/resources/nni.ex b/lib/nbn/resources/nni.ex index 0e69896..14443cf 100644 --- a/lib/nbn/resources/nni.ex +++ b/lib/nbn/resources/nni.ex @@ -25,15 +25,15 @@ defmodule DiffoExample.Nbn.Nni do extensions: [AshJsonApi.Resource], authorizers: [Ash.Policy.Authorizer] - json_api do - type "nni" - end - resource do description "An Ash Resource representing a Network-to-Network Interface (NNI)" plural_name :Nnis end + json_api do + type "nni" + end + structure do specification do id "f6a7b8c9-0d1e-4f2a-9b3c-5d6e7f8a9b0c" @@ -54,14 +54,6 @@ defmodule DiffoExample.Nbn.Nni do end end - attributes do - attribute :rsp_id, :string do - description "the owning RSP's id — nil for Perentie-managed infrastructure" - allow_nil? true - public? true - end - end - actions do create :build do description "creates a new NNI resource instance" @@ -100,6 +92,14 @@ defmodule DiffoExample.Nbn.Nni do end end + attributes do + attribute :rsp_id, :string do + description "the owning RSP's id — nil for Perentie-managed infrastructure" + allow_nil? true + public? true + end + end + def identifier() do DiffoExample.Nbn.Util.identifier("NNI") end diff --git a/lib/nbn/resources/nni_group.ex b/lib/nbn/resources/nni_group.ex index 451e8ca..a9721f9 100644 --- a/lib/nbn/resources/nni_group.ex +++ b/lib/nbn/resources/nni_group.ex @@ -27,15 +27,15 @@ defmodule DiffoExample.Nbn.NniGroup do extensions: [AshJsonApi.Resource], authorizers: [Ash.Policy.Authorizer] - json_api do - type "nniGroup" - end - resource do description "An Ash Resource representing an NNI Group" plural_name :NniGroups end + json_api do + type "nniGroup" + end + structure do specification do id "e5f6a7b8-9c0d-4e1f-8a2b-4c5d6e7f8a9b" @@ -57,14 +57,6 @@ defmodule DiffoExample.Nbn.NniGroup do end end - attributes do - attribute :rsp_id, :string do - description "the owning RSP's id — nil for Perentie-managed infrastructure" - allow_nil? true - public? true - end - end - actions do create :build do description "creates a new NNI Group resource instance" @@ -113,5 +105,13 @@ defmodule DiffoExample.Nbn.NniGroup do end end + attributes do + attribute :rsp_id, :string do + description "the owning RSP's id — nil for Perentie-managed infrastructure" + allow_nil? true + public? true + end + end + use DiffoExample.Nbn.RspOwnership end diff --git a/lib/nbn/resources/ntd.ex b/lib/nbn/resources/ntd.ex index 7a5a445..ce3f294 100644 --- a/lib/nbn/resources/ntd.ex +++ b/lib/nbn/resources/ntd.ex @@ -26,15 +26,25 @@ defmodule DiffoExample.Nbn.Ntd do extensions: [AshJsonApi.Resource], authorizers: [Ash.Policy.Authorizer] - json_api do - type "ntd" - end - resource do description "An Ash Resource representing a Network Termination Device (NTD)" plural_name :Ntds end + policies do + bypass DiffoExample.Nbn.Checks.NoActor do + authorize_if always() + end + + bypass actor_attribute_equals(:role, :admin) do + authorize_if always() + end + + policy action_type(:read) do + authorize_if always() + end + end + structure do specification do id "c3d4e5f6-7a8b-4c9d-ae0f-2a3b4c5d6e7f" @@ -56,6 +66,14 @@ defmodule DiffoExample.Nbn.Ntd do end end + json_api do + type "ntd" + end + + def identifier() do + DiffoExample.Nbn.Util.identifier("NTD") + end + actions do create :build do description "creates a new NTD resource instance" @@ -103,22 +121,4 @@ defmodule DiffoExample.Nbn.Ntd do end) end end - - def identifier() do - DiffoExample.Nbn.Util.identifier("NTD") - end - - policies do - bypass DiffoExample.Nbn.Checks.NoActor do - authorize_if always() - end - - bypass actor_attribute_equals(:role, :admin) do - authorize_if always() - end - - policy action_type(:read) do - authorize_if always() - end - end end diff --git a/lib/nbn/resources/rsp.ex b/lib/nbn/resources/rsp.ex index c00c6a8..7b87329 100644 --- a/lib/nbn/resources/rsp.ex +++ b/lib/nbn/resources/rsp.ex @@ -23,6 +23,32 @@ defmodule DiffoExample.Nbn.Rsp do extensions: [AshStateMachine, AshJsonApi.Resource], fragments: [Diffo.Provider.BaseParty] + policies do + bypass DiffoExample.Nbn.Checks.NoActor do + authorize_if always() + end + + bypass actor_attribute_equals(:role, :admin) do + authorize_if always() + end + + policy action_type(:read) do + authorize_if always() + end + end + + field_policies do + field_policy :state do + authorize_if DiffoExample.Nbn.Checks.NoActor + authorize_if actor_attribute_equals(:role, :admin) + authorize_if expr(^actor(:id) == id) + end + + field_policy :* do + authorize_if always() + end + end + # BaseParty provides: # data_layer: AshNeo4j.DataLayer # extensions: AshJason.Resource, AshOutstanding.Resource, Diffo.Provider.Party.Extension @@ -36,40 +62,13 @@ defmodule DiffoExample.Nbn.Rsp do type "rsp" end - state_machine do - initial_states [:inactive] - default_initial_state :inactive - state_attribute :state - - transitions do - transition action: :activate, from: [:inactive, :suspended], to: :active - transition action: :suspend, from: :active, to: :suspended - transition action: :deactivate, from: [:active, :suspended], to: :inactive - end - end - - attributes do - attribute :short_name, :atom do - description "atom identifier used as the actor for authorisation" - allow_nil? false - public? true - end - - attribute :state, :atom do - allow_nil? false - default :inactive - public? true - constraints [one_of: [:active, :suspended, :inactive]] - end - end - instances do role :owner, DiffoExample.Nbn.Avc # pending resolution of /diffo-dev/diffo#101 - #role :owner, DiffoExample.Nbn.Cvc - #role :owner, DiffoExample.Nbn.Nni - #role :owner, DiffoExample.Nbn.NniGroup - #role :owner, DiffoExample.Nbn.NbnEthernet + # role :owner, DiffoExample.Nbn.Cvc + # role :owner, DiffoExample.Nbn.Nni + # role :owner, DiffoExample.Nbn.NniGroup + # role :owner, DiffoExample.Nbn.NbnEthernet end actions do @@ -77,6 +76,7 @@ defmodule DiffoExample.Nbn.Rsp do accept [:name, :short_name, :id] upsert? true change set_attribute(:type, :Organization) + validate match(:id, ~r/^\d{4}$/) do message "must be a four-digit EPID" end @@ -102,34 +102,35 @@ defmodule DiffoExample.Nbn.Rsp do end end - identities do - identity :unique_name, [:name] - identity :unique_short_name, [:short_name] - end + state_machine do + initial_states [:inactive] + default_initial_state :inactive + state_attribute :state - policies do - bypass DiffoExample.Nbn.Checks.NoActor do - authorize_if always() + transitions do + transition action: :activate, from: [:inactive, :suspended], to: :active + transition action: :suspend, from: :active, to: :suspended + transition action: :deactivate, from: [:active, :suspended], to: :inactive end + end - bypass actor_attribute_equals(:role, :admin) do - authorize_if always() + attributes do + attribute :short_name, :atom do + description "atom identifier used as the actor for authorisation" + allow_nil? false + public? true end - policy action_type(:read) do - authorize_if always() + attribute :state, :atom do + allow_nil? false + default :inactive + public? true + constraints one_of: [:active, :suspended, :inactive] end end - field_policies do - field_policy :state do - authorize_if DiffoExample.Nbn.Checks.NoActor - authorize_if actor_attribute_equals(:role, :admin) - authorize_if expr(^actor(:id) == id) - end - - field_policy :* do - authorize_if always() - end + identities do + identity :unique_name, [:name] + identity :unique_short_name, [:short_name] end end diff --git a/lib/nbn/resources/uni.ex b/lib/nbn/resources/uni.ex index afaefa6..45ad7bf 100644 --- a/lib/nbn/resources/uni.ex +++ b/lib/nbn/resources/uni.ex @@ -26,15 +26,25 @@ defmodule DiffoExample.Nbn.Uni do extensions: [AshJsonApi.Resource], authorizers: [Ash.Policy.Authorizer] - json_api do - type "uni" - end - resource do description "An Ash Resource representing a User Network Interface (UNI)" plural_name :Unis end + policies do + bypass DiffoExample.Nbn.Checks.NoActor do + authorize_if always() + end + + bypass actor_attribute_equals(:role, :admin) do + authorize_if always() + end + + policy action_type(:read) do + authorize_if always() + end + end + structure do specification do id "a1b2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d" @@ -55,6 +65,29 @@ defmodule DiffoExample.Nbn.Uni do end end + json_api do + type "uni" + end + + def identifier() do + DiffoExample.Nbn.Util.identifier("UNI") + end + + # mines related resource to characteristics + def mine_related(changeset, _context) when is_struct(changeset, Ash.Changeset) do + uni = Ash.load!(changeset.data, reverse_relationships: [:characteristics]) + + ntd_relationship = hd(uni.reverse_relationships) + + port = {:port, Diffo.Unwrap.unwrap(hd(ntd_relationship.characteristics).value)} + {:ok, ntd} = Diffo.Provider.get_instance_by_id(ntd_relationship.source_id) + technology = {:technology, Util.extract(ntd.characteristics, :ntd, :technology)} + + Ash.Changeset.force_set_argument(changeset, :characteristic_value_updates, + uni: [port, technology] + ) + end + actions do create :build do description "creates a new UNI resource instance" @@ -106,37 +139,4 @@ defmodule DiffoExample.Nbn.Uni do end) end end - - def identifier() do - DiffoExample.Nbn.Util.identifier("UNI") - end - - # mines related resource to characteristics - def mine_related(changeset, _context) when is_struct(changeset, Ash.Changeset) do - uni = Ash.load!(changeset.data, [reverse_relationships: [:characteristics]]) - - ntd_relationship = hd(uni.reverse_relationships) - - port = {:port, Diffo.Unwrap.unwrap(hd(ntd_relationship.characteristics).value)} - {:ok, ntd} = Diffo.Provider.get_instance_by_id(ntd_relationship.source_id) - technology = {:technology, Util.extract(ntd.characteristics, :ntd, :technology)} - - Ash.Changeset.force_set_argument(changeset, :characteristic_value_updates, - uni: [port, technology] - ) - end - - policies do - bypass DiffoExample.Nbn.Checks.NoActor do - authorize_if always() - end - - bypass actor_attribute_equals(:role, :admin) do - authorize_if always() - end - - policy action_type(:read) do - authorize_if always() - end - end end diff --git a/mix.exs b/mix.exs index 4b7e26b..896b929 100644 --- a/mix.exs +++ b/mix.exs @@ -25,6 +25,8 @@ defmodule DiffoExample.MixProject do homepage_url: "http://diffo.dev/diffo_example/", docs: [main: "readme", extras: ["README.md"]], elixirc_paths: elixirc_paths(Mix.env()), + # agent stuff + usage_rules: usage_rules(), # hex.pm stuff deps: deps(), docs: &docs/0, @@ -89,9 +91,10 @@ defmodule DiffoExample.MixProject do {:diffo, diffo_version("~> 0.2.2")}, {:ash_json_api, "~> 1.6"}, {:plug_cowboy, "~> 2.7"}, - {:req, "~> 0.5", only: [:dev, :test]}, {:picosat_elixir, "~> 0.2.0"}, {:simple_sat, ">= 0.0.0"}, + {:usage_rules, "~> 1.0", only: [:dev]}, + {:req, "~> 0.5", only: [:dev, :test]}, {:igniter, "~> 0.6", only: [:dev, :test]}, {:ex_doc, "~> 0.37", only: [:dev, :test], runtime: false} ] @@ -113,4 +116,31 @@ defmodule DiffoExample.MixProject do defp elixirc_paths(:test), do: elixirc_paths(:dev) ++ ["test/support"] defp elixirc_paths(_), do: ["lib"] + + defp usage_rules do + # Example for those using claude. + [ + file: "CLAUDE.md", + # rules to include directly in CLAUDE.md + usage_rules: ["usage_rules:all"], + skills: [ + location: ".claude/skills", + # build skills that combine multiple usage rules + build: [ + "ash-framework": [ + description: + "Use this skill working with Ash Framework or any of its extensions. Always consult this when making any domain changes, features or fixes.", + # Include all Ash dependencies + usage_rules: [:ash, ~r/^ash_/] + ], + "diffo-framework": [ + description: + "Use this skill working with Diffo or any related non-Ash Diffo components. Understand the provider extension and assigner.", + # Include all Diffo dependencies + usage_rules: [:diffo, ~r/^diffo_/] + ] + ] + ] + ] + end end diff --git a/mix.lock b/mix.lock index a733002..e851b09 100644 --- a/mix.lock +++ b/mix.lock @@ -56,6 +56,7 @@ "stream_data": {:hex, :stream_data, "1.3.0", "bde37905530aff386dea1ddd86ecbf00e6642dc074ceffc10b7d4e41dfd6aac9", [:mix], [], "hexpm", "3cc552e286e817dca43c98044c706eec9318083a1480c52ae2688b08e2936e3c"}, "telemetry": {:hex, :telemetry, "1.4.1", "ab6de178e2b29b58e8256b92b382ea3f590a47152ca3651ea857a6cae05ac423", [:rebar3], [], "hexpm", "2172e05a27531d3d31dd9782841065c50dd5c3c7699d95266b2edd54c2dafa1c"}, "text_diff": {:hex, :text_diff, "0.1.0", "1caf3175e11a53a9a139bc9339bd607c47b9e376b073d4571c031913317fecaa", [:mix], [], "hexpm", "d1ffaaecab338e49357b6daa82e435f877e0649041ace7755583a0ea3362dbd7"}, + "usage_rules": {:hex, :usage_rules, "1.2.6", "a7b3f8d6e5d265701139d5714749c37c54bb82230a4c51ec54a12a1e4769b9d1", [:mix], [{:igniter, ">= 0.6.6 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}], "hexpm", "608411b9876a16a9d62a427dbaf42faf458e4cd0a508b3bd7e5ee71502073582"}, "uuid": {:hex, :uuid, "1.1.8", "e22fc04499de0de3ed1116b770c7737779f226ceefa0badb3592e64d5cfb4eb9", [:mix], [], "hexpm", "c790593b4c3b601f5dc2378baae7efaf5b3d73c4c6456ba85759905be792f2ac"}, "websock": {:hex, :websock, "0.5.3", "2f69a6ebe810328555b6fe5c831a851f485e303a7c8ce6c5f675abeb20ebdadc", [:mix], [], "hexpm", "6105453d7fac22c712ad66fab1d45abdf049868f253cf719b625151460b8b453"}, "websock_adapter": {:hex, :websock_adapter, "0.5.9", "43dc3ba6d89ef5dec5b1d0a39698436a1e856d000d84bf31a3149862b01a287f", [:mix], [{:bandit, ">= 0.6.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "5534d5c9adad3c18a0f58a9371220d75a803bf0b9a3d87e6fe072faaeed76a08"}, diff --git a/test/nbn/rsp_test.exs b/test/nbn/rsp_test.exs index 20825d4..8854ce2 100644 --- a/test/nbn/rsp_test.exs +++ b/test/nbn/rsp_test.exs @@ -21,7 +21,8 @@ defmodule DiffoExample.Nbn.RspTest do describe "RSP resource" do test "create and activate an RSP" do - {:ok, rsp} = Nbn.create_rsp(%{name: "Wedge-tail Telecom", short_name: :wedgetail, id: "8001"}) + {:ok, rsp} = + Nbn.create_rsp(%{name: "Wedge-tail Telecom", short_name: :wedgetail, id: "8001"}) assert is_struct(rsp, Rsp) assert rsp.state == :inactive @@ -33,7 +34,8 @@ defmodule DiffoExample.Nbn.RspTest do end test "RSP state machine: activate → suspend → deactivate" do - {:ok, rsp} = Nbn.create_rsp(%{name: "Wedge-tail Telecom", short_name: :wedgetail, id: "8001"}) + {:ok, rsp} = + Nbn.create_rsp(%{name: "Wedge-tail Telecom", short_name: :wedgetail, id: "8001"}) {:ok, rsp} = Nbn.activate_rsp(rsp) assert rsp.state == :active @@ -99,7 +101,9 @@ defmodule DiffoExample.Nbn.RspTest do {:ok, resource} = Nbn.build_nbn_ethernet(%{}, actor: wedgetail) assert {:error, %Ash.Error.Forbidden{}} = - Nbn.define_nbn_ethernet(resource, %{characteristic_value_updates: []}, actor: quokka) + Nbn.define_nbn_ethernet(resource, %{characteristic_value_updates: []}, + actor: quokka + ) end test "nil actor (internal call) can read any RSP's resource", %{wedgetail: wedgetail} do