Macula Resource Identifiers (MRI)
View SourceTyped, hierarchical, queryable identifiers for all resources in the Macula ecosystem.
Overview
MRI provides a unified identification scheme that is:
- Typed - Resource type embedded in identifier, no external lookup needed
- Hierarchical - Natural parent-child relationships via path structure
- Queryable - Efficient tree and graph queries
- Extensible - New types and relationships without code changes
- Decentralized - No central authority required for creation
- BEAM-native - Efficient binary representation, pattern matching friendly
Format
mri:{type}:{realm}/{path}| Component | Description | Rules |
|---|---|---|
mri | Literal prefix | Always mri |
type | Resource type | Lowercase, from registry |
realm | Mesh domain | Reverse-domain notation |
path | Hierarchical path | Slash-separated segments |
Path Segment Rules
segment = 1-63 characters
= lowercase alphanumeric, hyphen, underscore
= no leading/trailing hyphen
= DNS-compatible
path = segment *("/" segment)
= max 10 levels deepExamples
%% Realms and organizations
<<"mri:realm:io.macula">>
<<"mri:org:io.macula/acme">>
%% Users and apps
<<"mri:user:io.macula/acme/alice">>
<<"mri:app:io.macula/acme/counter">>
<<"mri:service:io.macula/acme/counter/api">>
%% Physical infrastructure
<<"mri:device:io.macula/citypower/cabinet-ams-42">>
<<"mri:location:io.macula/citypower/nl/amsterdam/centrum">>
<<"mri:cluster:io.macula/citypower/amsterdam-grid">>
%% Artifacts and security
<<"mri:artifact:io.macula/acme/counter-v1.2.0">>
<<"mri:license:io.macula/acme/counter/lic-001">>
<<"mri:cert:io.macula/acme/cert-2026-001">>
%% Messaging
<<"mri:topic:io.macula/acme/events.orders.created">>
<<"mri:proc:io.macula/acme/api.users.get">>
%% Taxonomy
<<"mri:class:io.macula/device/edge-device/street-cabinet">>Type Registry
Logical Resources
| Type | Path Schema | Example |
|---|---|---|
realm | (none) | mri:realm:io.macula |
org | {org} | mri:org:io.macula/acme |
user | {org}/{user} | mri:user:io.macula/acme/alice |
app | {org}/{app} | mri:app:io.macula/acme/counter |
service | {org}/{app}/{svc} | mri:service:io.macula/acme/counter/api |
Artifacts & Security
| Type | Path Schema | Example |
|---|---|---|
artifact | {org}/{id} | mri:artifact:io.macula/acme/counter-v1.2.0 |
license | {org}/{app}/{id} | mri:license:io.macula/acme/counter/lic-001 |
cert | {org}/{id} | mri:cert:io.macula/acme/cert-2026-001 |
key | {org}/{id} | mri:key:io.macula/acme/signing-key-001 |
Messaging
| Type | Path Schema | Example |
|---|---|---|
topic | {org}/{path} | mri:topic:io.macula/acme/events.orders |
proc | {org}/{path} | mri:proc:io.macula/acme/api.users.get |
content | {mcid} | mri:content:io.macula/Qm...abc |
Physical Infrastructure
| Type | Path Schema | Example |
|---|---|---|
device | {org}/{id} | mri:device:io.macula/citypower/cabinet-001 |
cluster | {org}/{name} | mri:cluster:io.macula/citypower/amsterdam-grid |
location | {org}/{path} | mri:location:io.macula/citypower/nl/amsterdam |
zone | {org}/{name} | mri:zone:io.macula/citypower/production-dmz |
network | {org}/{name} | mri:network:io.macula/citypower/subnet-10.0.1.0 |
AI/ML
| Type | Path Schema | Example |
|---|---|---|
model | {org}/{name} | mri:model:io.macula/acme/classifier-v2 |
dataset | {org}/{name} | mri:dataset:io.macula/acme/training-2026-01 |
config | {org}/{path} | mri:config:io.macula/acme/app.settings |
Taxonomy
| Type | Path Schema | Example |
|---|---|---|
class | {path} | mri:class:io.macula/edge-device/street-cabinet |
taxonomy | {org}/{name} | mri:taxonomy:io.macula/acme/device-classification |
Core API
Parsing
%% Parse MRI into components
{ok, #{type := app,
realm := <<"io.macula">>,
path := [<<"acme">>, <<"counter">>]}} =
macula_mri:parse(<<"mri:app:io.macula/acme/counter">>).
%% Validation
ok = macula_mri:validate(<<"mri:app:io.macula/acme/counter">>).
{error, invalid_type} = macula_mri:validate(<<"mri:unknown:io.macula/x">>).Component Access
MRI = <<"mri:service:io.macula/acme/counter/api">>,
app = macula_mri:type(MRI),
<<"io.macula">> = macula_mri:realm(MRI),
[<<"acme">>, <<"counter">>, <<"api">>] = macula_mri:path(MRI).Hierarchy
%% Parent resolution
<<"mri:app:io.macula/acme/counter">> =
macula_mri:parent(<<"mri:service:io.macula/acme/counter/api">>).
<<"mri:org:io.macula/acme">> =
macula_mri:parent(<<"mri:app:io.macula/acme/counter">>).
undefined = macula_mri:parent(<<"mri:realm:io.macula">>).
%% Ancestry check
true = macula_mri:is_ancestor(
<<"mri:org:io.macula/acme">>,
<<"mri:service:io.macula/acme/counter/api">>
).Construction
%% Type-specific constructors
<<"mri:realm:io.macula">> = macula_mri:new_realm(<<"io.macula">>).
<<"mri:org:io.macula/acme">> =
macula_mri:new_org(<<"io.macula">>, <<"acme">>).
<<"mri:app:io.macula/acme/counter">> =
macula_mri:new_app(<<"io.macula">>, <<"acme">>, <<"counter">>).
%% Generic constructor
<<"mri:device:io.macula/citypower/cabinet-001">> =
macula_mri:new(device, #{
realm => <<"io.macula">>,
path => [<<"citypower">>, <<"cabinet-001">>]
}).Hierarchical Relationships
MRIs encode ownership through path structure:
mri:realm:io.macula
├── mri:org:io.macula/acme
│ ├── mri:user:io.macula/acme/alice
│ ├── mri:user:io.macula/acme/bob
│ └── mri:app:io.macula/acme/counter
│ ├── mri:service:io.macula/acme/counter/api
│ ├── mri:license:io.macula/acme/counter/lic-001
│ └── mri:artifact:io.macula/acme/counter-v1.0.0
└── mri:org:io.macula/citypower
├── mri:device:io.macula/citypower/cabinet-001
└── mri:location:io.macula/citypower/nl/amsterdamGraph Relationships
Beyond hierarchy, MRIs support cross-cutting relationships:
Relationship Types
Spatial:
located_at/containsadjacent_to
Organizational:
member_of/has_membermanages/managed_byowns/owned_by
Technical:
depends_on/depended_on_byprovides/provided_byconsumes/consumed_byconnects_to
Data:
trained_on/used_to_trainderived_from/source_ofversion_of
Taxonomic:
instance_of/has_instancesubclass_of/superclass_ofcategorized_as/categorizes
Custom Relationships
%% Built-in predicates are atoms
{depends_on, <<"mri:app:...">>}
%% Custom predicates use tagged tuple
{{custom, <<"powered_by">>}, <<"mri:device:...">>}Storage Behaviours
The core library defines behaviours; storage is pluggable.
macula_mri_store Behaviour
-callback register(mri(), metadata()) -> ok | {error, term()}.
-callback lookup(mri()) -> {ok, metadata()} | {error, not_found}.
-callback update(mri(), metadata()) -> ok | {error, term()}.
-callback delete(mri()) -> ok | {error, term()}.
-callback exists(mri()) -> boolean().
-callback list_children(mri()) -> [mri()].
-callback list_descendants(mri()) -> [mri()].
-callback list_by_type(mri_type(), realm()) -> [mri()].macula_mri_graph Behaviour
-callback create_relationship(mri(), predicate(), mri()) -> ok | {error, term()}.
-callback delete_relationship(mri(), predicate(), mri()) -> ok | {error, term()}.
-callback related_to(mri(), predicate()) -> [mri()].
-callback related_from(mri(), predicate()) -> [mri()].
-callback traverse_transitive(mri(), predicate(), direction()) -> [mri()].Available Adapters
| Adapter | Package | Use Case |
|---|---|---|
macula_mri_ets | macula/ (bundled) | Development, testing |
macula_mri_khepri | macula_mri_khepri | Production (distributed Raft) |
| Custom | Your package | Implement behaviours |
Configuration
%% In application config
{macula, [
{mri_store_adapter, macula_mri_khepri_store},
{mri_graph_adapter, macula_mri_khepri_graph}
]}.Extensibility
Runtime Type Registration
%% Register custom type without code changes
macula_mri_registry:register_type(<<"streetlight">>, #{
description => <<"Street lighting fixture">>,
path_schema => [org, device_id],
parent_type => device
}).
%% Now valid:
<<"mri:streetlight:io.macula/citypower/light-001">>Namespace Isolation
%% Realm-specific types
macula_mri_registry:register_type(<<"turbine">>, #{
realm => <<"io.windpower">>,
path_schema => [org, farm, turbine_id]
}).
%% Valid in io.windpower realm only
<<"mri:turbine:io.windpower/acme/north-farm/turbine-001">>Best Practices
MRI Design
- Meaningful segments -
/acme/counternot/a/c - Shallow paths - Aim for 2-4 segments, max 10
- Lowercase only - All segments lowercase
- DNS-compatible - Alphanumeric, hyphen, underscore
- No PII - Use IDs, not email addresses
Type Selection
- Use existing types when they fit
- Check parent type for inheritance
- Consider queryability - type affects indexes
- Document custom types in your realm
Relationships
- Prefer standard predicates over custom
- Add metadata for context (version, timestamp)
- Consider both directions - forward and reverse queries
- Use taxonomy for classification