Skip to content

BaseCharacteristic: typed Ash resource fragment for characteristics #128

@matt-beanland

Description

@matt-beanland

Motivation

The provider DSL declaration `characteristic :foo, SomeValueModule` currently assumes `SomeValueModule` is a plain struct used only to seed a `Diffo.Type.Value` dynamic stored on the generic `Diffo.Provider.Characteristic` resource. This means:

  • Characteristic values are opaque to Ash — no native filtering, sorting, or expression calculations on typed fields.
  • `TransformBehaviour` / `Extension.Characteristic` exist largely to work around the dynamic, creating and relating characteristics via custom wiring rather than standard Ash relationship management.
  • The assigner has to spelunk through `Diffo.Type.Value` internals to update individual fields, and the `parent()` / embedded resource approach to liveness doesn't work.

Proposed change

Introduce `Diffo.Provider.BaseCharacteristic` as an Ash Resource Fragment (parallel to `BaseInstance` / `BaseParty` / `BasePlace`). Characteristic type modules become Ash Resources that `use` `BaseCharacteristic`, gaining real typed Ash attributes and standard Ash relationship management.

What stays

`Diffo.Provider.Characteristic` (the existing generic concrete resource, using `Diffo.Type.Value`) is retained as-is. It may itself extend `BaseCharacteristic`, making it one valid option alongside typed resources. The DSL just won't force this approach — it accepts any `BaseCharacteristic`-derived resource, so the caller chooses typed, union, or dynamic depending on what the characteristic shape demands.

DSL impact

`characteristic :foo, SomeModule` continues to work syntactically. `SomeModule` must be a `BaseCharacteristic`-derived resource; `Diffo.Provider.Characteristic` remains available as the dynamic option. `VerifyCharacteristics` is updated to check `BaseCharacteristic` extension.

Instance behaviour simplification

With characteristics as proper resources, creating an instance's characteristics becomes standard `manage_relationship` on the Instance's create action. The custom `set_characteristics_argument` / `create_characteristics_from_declarations` wiring in `Extension.Characteristic` can be removed or simplified. `TransformBehaviour` and `ActionCreate` simplify accordingly.

Neo4j model

Each `BaseCharacteristic`-derived resource becomes a distinct Neo4j node label. `Diffo.Provider.Characteristic` continues to produce `:Characteristic` nodes for the dynamic case.

Acceptance criteria

  • `Diffo.Provider.BaseCharacteristic` fragment defined
  • `Diffo.Provider.Characteristic` extended from `BaseCharacteristic` (or compatible with it)
  • Typed characteristic modules (e.g. `ShelfValue`) migrated to extend `BaseCharacteristic`
  • `VerifyCharacteristics` updated to check `BaseCharacteristic` extension
  • `Extension.Characteristic` wiring simplified; replaced by standard relationship management where applicable
  • `TransformBehaviour` / `ActionCreate` / `ActionUpdate` simplified
  • Tests updated; existing characteristic integration tests pass
  • DSL cheat sheet and livebook updated

Sequencing

  1. This issue (BaseCharacteristic: typed Ash resource fragment for characteristics #128) — foundational
  2. Assigner refactor (refactor assigner #120) — `AssignableValue` becomes a `BaseCharacteristic`-derived resource; fields, aggregates, and calculations live directly on it, removing all `Ash.Type.Dynamic` / `parent()` workarounds
  3. BaseFeature (BaseFeature: typed Ash resource fragment for features #129) — depends on this issue

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions