Skip to content
Merged

dev #47

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
31 changes: 13 additions & 18 deletions .formatter.exs
Original file line number Diff line number Diff line change
Expand Up @@ -4,29 +4,24 @@

# 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
tool: 3,
tool: 4
]

[
plugins: [Spark.Formatter],
inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"],
import_deps: [:ash],
import_deps: [
:diffo,
:ash,
:ash_ai,
: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
Expand Down
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,5 @@ diffo-*.tar
.DS_Store

# Agent related
.claude/*
.claude/*
CLAUDE*
14 changes: 14 additions & 0 deletions .igniter.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# SPDX-FileCopyrightText: 2025 diffo_example contributors <https://github.com/diffo-dev/diffo_example/graphs.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"]
]
73 changes: 73 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
<!--
SPDX-FileCopyrightText: 2025 diffo_example contributors <https://github.com/diffo-dev/diffo_example/graphs.contributors>

SPDX-License-Identifier: MIT
-->

# Agents working in this repo

Notes for AI assistants (Claude Code, Cursor, Continue, etc.) and humans pairing with them.

## Keep `tools do` aligned with `define`d actions

Each Ash domain in this repo (`DiffoExample.Access`, `DiffoExample.Nbn`) declares two parallel lists:

1. **`resources do ... resource X do define :name, action: :foo end end`** — the code-interface surface, generating `MyDomain.name/...` functions for Elixir callers.
2. **`tools do tool :name, X, :foo end`** — the MCP surface, exposing the same actions to AI agents via [`ash_ai`](https://hexdocs.pm/ash_ai).

**When you add a new action to a resource, add it to BOTH places.** Forgetting to add the `tool` entry leaves the action invisible to MCP clients — the test suite won't catch it, the code compiles, the action works for Elixir callers, and the AI silently can't reach it.

The convention is:

- One `tool` entry per `define`-d action.
- Tool name matches the code-interface name where possible (e.g. `define :build_cable` ↔ `tool :build_cable, Cable, :build`). Disambiguate with a suffix where two resources have actions of the same name (e.g. `tool :assign_port_on_card` and `tool :assign_port_on_ntd`).
- Read actions exposed as their natural read names (`tool :get_cable_by_id, Cable, :read`).

## Why the discipline

Tool-via-existing-action is the WWZD-shaped pattern: authorisation, validation, after-action choreography, multi-tenancy, polymorphism — all inherited from the action without writing AI-specific logic. The AI gets the same access an RSP-actor consumer has, no more and no less. That property only holds if every action the AI should be able to reach is declared as a tool.

## When NOT to add a tool

- **Internal-only actions** that no consumer (Elixir, HTTP, MCP) should call directly. These typically don't have a `define` either.
- **Provider primitives** (`Diffo.Provider.create_party`, `Diffo.Provider.create_place`) — deliberately not exposed via Access/Nbn MCP. When Access/Nbn grow their own party/place relationship-management actions, expose those instead, and let authorisation gate what each actor can do.
- **Duplicate `define`s wrapping the same action** — e.g. `define :get_rsp_by_epid, action: :read, get_by: :id` and `define :get_rsp_by_short_name, action: :read, get_by: :short_name` both wrap the `:read` action with different filters. One tool (`tool :get_rsp_by_epid, Rsp, :read`) covers both — the AI can supply the filter shape it needs via tool arguments. The code-interface defines are an Elixir convenience; the tool exposes the underlying action.

## Where this matters most

If you're modifying:

- A `*.ex` file under `lib/access/resources/`, `lib/access/services/`, or `lib/nbn/resources/` that adds/renames a `define` in its domain — also touch `lib/access/access.ex` or `lib/nbn/nbn.ex` and update the `tools do` block.
- The `tools do` block itself — make sure the resource module is aliased at the top of the domain file.

## Quick check

After changes, count alignment:

```bash
# tools declared
grep -c "tool :" lib/access/access.ex lib/nbn/nbn.ex

# actions code-interface-defined
grep -c "define :" lib/access/access.ex lib/nbn/nbn.ex
```

The two should match (modulo intentional exclusions).

## Before you commit

Two checks before any commit, every commit:

```bash
mix format # auto-format every changed file to project style
reuse lint # ensure every file has SPDX-FileCopyrightText + SPDX-License-Identifier
```

New `.ex` / `.exs` files start with a comment header matching the existing
files (see `lib/diffo_example/util.ex` for the canonical form). New markdown
files use an HTML-comment variant (see `README.md`). `reuse lint` will tell
you which files are missing copyright/license info; if you've created a
new file and haven't added the header, this is the place to catch it.

Forgetting either is the easiest way to introduce CI noise the reviewer
has to clean up. Save them both the time.
35 changes: 35 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,41 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline

<!-- changelog -->

## [v0.2.2](https://github.com/diffo-dev/diffo/compare/v0.2.1..v0.2.2) (2026-05-21)

### Maintenance:
* updated to diffo 0.4.0 (skipping 0.3.0)
* updated to ash_neo4j 0.6.0
* added ash_ai 0.6 for MCP and future LLM features
* AGENTS.md added with discipline reminders (keep `tools do` aligned with code-interface `define`s; run `mix format` and `reuse lint` before every commit)
* `DiffoExample.DataCase` ExUnit case template — DRYs the AshNeo4j sandbox setup across 7 test files
* `test/support/characteristics.ex` `@characteristic_modules` derived from configured ash_domains at runtime via `Ash.Domain.Info.resources/1` + `Diffo.Provider.Extension.Info.instance?/1`

### Features:
* diffo 0.4.0 migration: unified `provider do` DSL, `pools do`, `AssignmentRelationship`, `Assigner.assign/3` with pool lookup, `relationships do` permitted source/target roles, `Pool.update_pools/3` in define actions
* `DiffoExample.Changes.Define`, `Relate`, `Assign` — three change modules collapsing ~28 hand-written after-action bodies across 11 instance resources into one declarative line per action
* MCP interface via ash_ai (issue #44) — 60 tools exposed across Access and Nbn domains; `/mcp` forwarded from the existing Plug.Cowboy listener on port 4000; setup how-to at `documentation/how_to/setup_mcp.md`
* Bring up characteristics across the assignment graph (issues #10 and #32):
* `DiffoExample.Calculations.InheritedCharacteristic` — assignee inherits typed characteristic from its assigner via incoming `AssignmentRelationship` traversal (mirrors `Diffo.Provider.Calculations.InheritedPlace`)
* `DiffoExample.Calculations.ReverseInheritedCharacteristic` — assigner brings up typed characteristic from its assignees via outgoing traversal ("insanity is hereditary, you get it from your kids")
* `DiffoExample.Access.Calculations.ShelfTotalPorts` — multi-hop aggregate-style calc summing port capacity across a shelf's assigned cards
* Card surfaces `:shelf`/`:slot`; Path surfaces `:card`/`:port` and (two-hop) `:shelf`; Shelf surfaces `:cards`/`:total_ports`
* Resource lifecycle: `:build` leaves `resource_state` nil; `:define` transitions to `:operating` — minimum needed for the Assigner gate without modelling intermediate planning states (which are better expressed as Plan entities outside the instance)
* `Shelf` and `Path` characteristics gained `:device_name` attribute with JSON rename to `"name"` (parallel to the `Dslam` pattern), so the typed characteristic carries the device's own name without colliding with `BaseCharacteristic`'s role-name `:name`

### Workarounds (raised upstream):
* `DslAccess.qualify_result` transitions to `:inactive` instead of `:feasibilityChecked` — works around the diffo Assigner gating services to `[:active, :inactive]`
* `DiffoExample.Util.summarise_characteristics/2` test-time projection collapses typed characteristics and pool records to `"absent_diffo_169"` placeholders on both sides of JSON comparisons while [diffo#169](https://github.com/diffo-dev/diffo/issues/169) is open
* No JSON rendering yet of inherited / reverse-inherited characteristic calc results — waits on [diffo#173](https://github.com/diffo-dev/diffo/issues/173)

### Upstream yarns raised:
* [diffo#169](https://github.com/diffo-dev/diffo/issues/169) — surface typed characteristics and pools in Instance JSON
* [diffo#170](https://github.com/diffo-dev/diffo/issues/170) — provide change modules for the Define / Relate / Assign patterns
* [diffo#171](https://github.com/diffo-dev/diffo/issues/171) — BaseCharacteristic could generate `update :update accept` from public attributes
* [diffo#172](https://github.com/diffo-dev/diffo/issues/172) — `inherited_characteristic` and `reverse_inherited_characteristic` DSL declarations
* [diffo#173](https://github.com/diffo-dev/diffo/issues/173) — JSON surfacing of inherited and reverse-inherited concepts
* [diffo-dev/ash_neo4j#74](https://github.com/diffo-dev/ash_neo4j/issues/74) — vector index support (enriched with consumer context)

## [v0.2.1](https://github.com/diffo-dev/diffo/compare/v0.2.0..v0.2.1) (2026-05-07)

### Maintenance:
Expand Down
6 changes: 6 additions & 0 deletions config/runtime.exs
Original file line number Diff line number Diff line change
Expand Up @@ -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") ||
Expand Down
Loading
Loading