Ash Resource for a TMF Service or Resource Specification.
A Specification identifies the kind of a TMF Service or Resource Instance. Every instance
carries a relationship to exactly one Specification node in the graph, established at build
time and changeable via Diffo.Provider.respecify_instance/2.
Identity
A Specification is uniquely identified by {name, major_version}. The id is a stable
UUID4 that is the same across all environments for a given {name, major_version} pair —
it is typically declared as a constant in the Instance Extension DSL and committed to source
control.
Versioning
Diffo uses semantic versioning for Specifications with three independent mechanisms:
| Change | Mechanism | Instance impact | Intended usage |
|---|---|---|---|
| Patch | next_patch_specification!/1 | None — internal fix | Corrections to metadata: description wording, category typos |
| Minor | next_minor_specification!/1 | None — all instances immediately reflect new version | Backward-compatible additions: new optional characteristics, new enum values |
| Major | New module, new id, new major_version | Instances stay on old spec until explicitly migrated | Breaking changes |
What constitutes a breaking change is deliberately vague — it depends on the specification domain and may require negotiation between provider and consumers.
Major version lifecycle
Major versions are decoupled across the provider/consumer boundary:
- Provider publishes V2 — deploys a new Instance kind module (e.g.
BroadbandV2) with the same specificationname, a newid, andmajor_version: 2. V1 and V2 coexist; both can be used to create instances. - Consumers adopt at their own pace — each consumer (e.g. an RSP) decides when to start creating V2 instances and when to migrate existing V1 instances.
- Provider withdraws V1 — removes the V1 module. Existing V1 instances remain in the graph and continue to operate; the domain API for creating new V1 instances is removed.
- Consumers complete migration — each consumer migrates remaining V1 instances to V2
via
Diffo.Provider.respecify_instance/2, handling any breaking data changes (e.g. remapping or removing an enum value) before or as part of the respecification.
create upsert behaviour
create_specification/1 uses upsert? true on the {name, major_version} identity.
Calling it for an existing {name, major_version} pair preserves any attributes not
supplied — a second call without category leaves the existing category intact.
An Ash Resource for a TMF Service or Resource Specification
Summary
Types
@type t() :: %Diffo.Provider.Specification{ __lateral_join_source__: term(), __meta__: term(), __metadata__: term(), __order__: term(), aggregates: term(), calculations: term(), category: term(), created_at: term(), description: term(), href: term(), id: term(), instance_type: term(), major_version: term(), minor_version: term(), name: term(), patch_version: term(), tmf_version: term(), type: term(), updated_at: term(), version: term() }
Functions
Derives the catalog prefix from the type
Examples
iex> Diffo.Provider.Specification.catalog(:serviceSpecification) :serviceCatalogManagement
iex> Diffo.Provider.Specification.catalog(:resourceSpecification) :resourceCatalogManagement
Validates that the keys in the provided input are valid for at least one action on the resource.
Raises a KeyError error at compile time if not. This exists because generally a struct should only ever
be created by Ash as a result of a successful action. You should not be creating records manually in code,
e.g %MyResource{value: 1, value: 2}. Generally that is fine, but often with embedded resources it is nice
to be able to validate the keys that are being provided, e.g
Resource
|> Ash.Changeset.for_create(:create, %{embedded: EmbeddedResource.input(foo: 1, bar: 2)})
|> Ash.create()
Same as input/1, except restricts the keys to values accepted by the action provided.