Skip to content

Commit d2ee7e5

Browse files
Merge pull request #47 from diffo-dev/dev
dev
2 parents d6e8603 + 5f29dff commit d2ee7e5

81 files changed

Lines changed: 3377 additions & 1372 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.formatter.exs

Lines changed: 13 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,29 +4,24 @@
44

55
# Used by "mix format"
66
locals_without_parens = [
7-
id: 1,
8-
category: 1,
9-
is_enabled?: 1,
10-
characteristic: 2,
11-
pick: 1,
12-
rename: 1,
13-
field: 3,
14-
expect: 1,
15-
relate: 1,
16-
translate: 1,
17-
guard: 1,
18-
customize: 1,
19-
order: 1,
20-
initial_states: 1,
21-
default_initial_state: 1,
22-
state_attribute: 1,
23-
transition: 1
7+
tool: 3,
8+
tool: 4
249
]
2510

2611
[
2712
plugins: [Spark.Formatter],
2813
inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"],
29-
import_deps: [:ash],
14+
import_deps: [
15+
:diffo,
16+
:ash,
17+
:ash_ai,
18+
:ash_state_machine,
19+
:ash_neo4j,
20+
:ash_jason,
21+
:ash_outstanding,
22+
:ash_json_api,
23+
:plug
24+
],
3025
locals_without_parens: locals_without_parens,
3126
export: [
3227
locals_without_parens: locals_without_parens

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,5 @@ diffo-*.tar
2727
.DS_Store
2828

2929
# Agent related
30-
.claude/*
30+
.claude/*
31+
CLAUDE*

.igniter.exs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# SPDX-FileCopyrightText: 2025 diffo_example contributors <https://github.com/diffo-dev/diffo_example/graphs.contributors>
2+
#
3+
# SPDX-License-Identifier: MIT
4+
5+
# This is a configuration file for igniter.
6+
# For option documentation, see https://hexdocs.pm/igniter/Igniter.Project.IgniterConfig.html
7+
# To keep it up to date, use `mix igniter.setup`
8+
[
9+
module_location: :outside_matching_folder,
10+
extensions: [],
11+
deps_location: :last_list_literal,
12+
source_folders: ["lib", "test/support"],
13+
dont_move_files: [~r"lib/mix"]
14+
]

AGENTS.md

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
<!--
2+
SPDX-FileCopyrightText: 2025 diffo_example contributors <https://github.com/diffo-dev/diffo_example/graphs.contributors>
3+
4+
SPDX-License-Identifier: MIT
5+
-->
6+
7+
# Agents working in this repo
8+
9+
Notes for AI assistants (Claude Code, Cursor, Continue, etc.) and humans pairing with them.
10+
11+
## Keep `tools do` aligned with `define`d actions
12+
13+
Each Ash domain in this repo (`DiffoExample.Access`, `DiffoExample.Nbn`) declares two parallel lists:
14+
15+
1. **`resources do ... resource X do define :name, action: :foo end end`** — the code-interface surface, generating `MyDomain.name/...` functions for Elixir callers.
16+
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).
17+
18+
**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.
19+
20+
The convention is:
21+
22+
- One `tool` entry per `define`-d action.
23+
- 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`).
24+
- Read actions exposed as their natural read names (`tool :get_cable_by_id, Cable, :read`).
25+
26+
## Why the discipline
27+
28+
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.
29+
30+
## When NOT to add a tool
31+
32+
- **Internal-only actions** that no consumer (Elixir, HTTP, MCP) should call directly. These typically don't have a `define` either.
33+
- **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.
34+
- **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.
35+
36+
## Where this matters most
37+
38+
If you're modifying:
39+
40+
- 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.
41+
- The `tools do` block itself — make sure the resource module is aliased at the top of the domain file.
42+
43+
## Quick check
44+
45+
After changes, count alignment:
46+
47+
```bash
48+
# tools declared
49+
grep -c "tool :" lib/access/access.ex lib/nbn/nbn.ex
50+
51+
# actions code-interface-defined
52+
grep -c "define :" lib/access/access.ex lib/nbn/nbn.ex
53+
```
54+
55+
The two should match (modulo intentional exclusions).
56+
57+
## Before you commit
58+
59+
Two checks before any commit, every commit:
60+
61+
```bash
62+
mix format # auto-format every changed file to project style
63+
reuse lint # ensure every file has SPDX-FileCopyrightText + SPDX-License-Identifier
64+
```
65+
66+
New `.ex` / `.exs` files start with a comment header matching the existing
67+
files (see `lib/diffo_example/util.ex` for the canonical form). New markdown
68+
files use an HTML-comment variant (see `README.md`). `reuse lint` will tell
69+
you which files are missing copyright/license info; if you've created a
70+
new file and haven't added the header, this is the place to catch it.
71+
72+
Forgetting either is the easiest way to introduce CI noise the reviewer
73+
has to clean up. Save them both the time.

CHANGELOG.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,41 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline
1111

1212
<!-- changelog -->
1313

14+
## [v0.2.2](https://github.com/diffo-dev/diffo/compare/v0.2.1..v0.2.2) (2026-05-21)
15+
16+
### Maintenance:
17+
* updated to diffo 0.4.0 (skipping 0.3.0)
18+
* updated to ash_neo4j 0.6.0
19+
* added ash_ai 0.6 for MCP and future LLM features
20+
* AGENTS.md added with discipline reminders (keep `tools do` aligned with code-interface `define`s; run `mix format` and `reuse lint` before every commit)
21+
* `DiffoExample.DataCase` ExUnit case template — DRYs the AshNeo4j sandbox setup across 7 test files
22+
* `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`
23+
24+
### Features:
25+
* 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
26+
* `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
27+
* 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`
28+
* Bring up characteristics across the assignment graph (issues #10 and #32):
29+
* `DiffoExample.Calculations.InheritedCharacteristic` — assignee inherits typed characteristic from its assigner via incoming `AssignmentRelationship` traversal (mirrors `Diffo.Provider.Calculations.InheritedPlace`)
30+
* `DiffoExample.Calculations.ReverseInheritedCharacteristic` — assigner brings up typed characteristic from its assignees via outgoing traversal ("insanity is hereditary, you get it from your kids")
31+
* `DiffoExample.Access.Calculations.ShelfTotalPorts` — multi-hop aggregate-style calc summing port capacity across a shelf's assigned cards
32+
* Card surfaces `:shelf`/`:slot`; Path surfaces `:card`/`:port` and (two-hop) `:shelf`; Shelf surfaces `:cards`/`:total_ports`
33+
* 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)
34+
* `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`
35+
36+
### Workarounds (raised upstream):
37+
* `DslAccess.qualify_result` transitions to `:inactive` instead of `:feasibilityChecked` — works around the diffo Assigner gating services to `[:active, :inactive]`
38+
* `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
39+
* No JSON rendering yet of inherited / reverse-inherited characteristic calc results — waits on [diffo#173](https://github.com/diffo-dev/diffo/issues/173)
40+
41+
### Upstream yarns raised:
42+
* [diffo#169](https://github.com/diffo-dev/diffo/issues/169) — surface typed characteristics and pools in Instance JSON
43+
* [diffo#170](https://github.com/diffo-dev/diffo/issues/170) — provide change modules for the Define / Relate / Assign patterns
44+
* [diffo#171](https://github.com/diffo-dev/diffo/issues/171) — BaseCharacteristic could generate `update :update accept` from public attributes
45+
* [diffo#172](https://github.com/diffo-dev/diffo/issues/172)`inherited_characteristic` and `reverse_inherited_characteristic` DSL declarations
46+
* [diffo#173](https://github.com/diffo-dev/diffo/issues/173) — JSON surfacing of inherited and reverse-inherited concepts
47+
* [diffo-dev/ash_neo4j#74](https://github.com/diffo-dev/ash_neo4j/issues/74) — vector index support (enriched with consumer context)
48+
1449
## [v0.2.1](https://github.com/diffo-dev/diffo/compare/v0.2.0..v0.2.1) (2026-05-07)
1550

1651
### Maintenance:

config/runtime.exs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@
44

55
import Config
66

7+
config :bolty, Bolt,
8+
uri: "bolt://localhost:7687",
9+
auth: [username: "neo4j", password: "password"],
10+
pool_size: 10,
11+
name: Bolt
12+
713
if config_env() == :prod do
814
database_url =
915
System.get_env("DATABASE_URL") ||

0 commit comments

Comments
 (0)