Provenance
Universal artifact lineage tracking for Elixir. Tracks what code produced what data, across modules, queries, requests, and jobs.
Every artifact gets a deterministic kind:identifier ID. An ETS-backed graph store
records producer/consumer relationships. A compile tracer automatically discovers
modules and classifies them into architectural layers.
Designed for use with Sentinel but works standalone in any Elixir project.
Installation
Add to your mix.exs:
def deps do
[
{:provenance, "~> 0.1"},
{:ecto, "~> 3.10", optional: true},
{:plug, "~> 1.15", optional: true}
]
endQuick Start
Registering Modules
Modules can be registered manually or discovered automatically via the compile tracer.
Provenance.register_module(MyApp.Orders,
source_file: "lib/my_app/orders.ex",
layer: :context,
dependencies: ["mod:MyApp.Repo"]
)
Provenance.register_module(MyApp.Orders.Order,
source_file: "lib/my_app/orders/order.ex",
layer: :schema
)
Provenance.lookup("mod:MyApp.Orders")
# => {:ok, %{id: "mod:MyApp.Orders", kind: :mod, layer: :context, ...}}Recording Provenance
Record that an artifact was produced by another:
Provenance.record("rec:orders:42", origin: "fn:MyApp.Orders.create/1")
Provenance.record("rec:order_items:99", origin: "fn:MyApp.Orders.add_item/2")Querying Related Artifacts
Provenance.related("fn:MyApp.Orders.create/1")
# => ["rec:orders:42"]
Provenance.related("mod:MyApp.Orders")
# => ["mod:MyApp.Repo"]
Provenance.module_graph()
# => %{"mod:MyApp.Orders" => ["mod:MyApp.Repo"], ...}Process Context
Attach provenance context to the current process. Useful for tracing requests, jobs, or any unit of work:
Provenance.put_context(request_id: "req:F1a2b3c4d5", user_id: "usr:42")
Provenance.get_context()
# => %{request_id: "req:F1a2b3c4d5", user_id: "usr:42"}
# Context merges -- existing keys are preserved, new ones added
Provenance.put_context(trace_id: "trc:abc")
Provenance.get_context()
# => %{request_id: "req:F1a2b3c4d5", user_id: "usr:42", trace_id: "trc:abc"}Artifact ID Format
All IDs follow the kind:identifier scheme:
| Kind | Format | Example |
|---|---|---|
mod | mod:<Module> | mod:MyApp.Orders |
fn | fn:<Module>.<function>/<arity> | fn:MyApp.Orders.get_order/1 |
tbl | tbl:<table> | tbl:orders |
col | col:<table>.<column> | col:orders.total_cents |
rec | rec:<table>:<pk> | rec:orders:42 |
req | req:<request_id> | req:F1a2b3c4d5 |
mig | mig:<migration_id> | mig:20240315_add_status |
job | job:<queue>:<job_id> | job:default:j-123 |
cfg | cfg:<config_path> | cfg:app.database.pool_size |
ID helpers:
Provenance.module_id(MyApp.Orders) # => "mod:MyApp.Orders"
Provenance.function_id(MyApp.Orders, :get_order, 1) # => "fn:MyApp.Orders.get_order/1"
Provenance.table_id("orders") # => "tbl:orders"
Provenance.record_id("orders", 42) # => "rec:orders:42"Compile Tracer
Automatically registers modules and records inter-module dependencies at compile time.
Enable in mix.exs:
def project do
[
# ...
compilers: Mix.compilers(),
tracers: [Provenance.CompileTracer]
]
endOr in config:
config :elixir, :tracers, [Provenance.CompileTracer]The tracer classifies each module into an architectural layer based on file path and module characteristics:
| Layer | Detection |
|---|---|
:test |
Path contains test/ |
:controller |
Path contains _web/controllers/ |
:view |
Path contains _web/live/ or _web/components/ |
:web |
Path contains _web/ |
:worker |
Path contains /workers/ |
:migration |
Path contains /migrations/ |
:repo |
Module name ends with Repo |
:schema |
Module exports __schema__/1 (Ecto) |
:context | Shallow lib path (depth <= 2) |
:lib | Everything else |
Architecture
+---------------------+
| Your Application |
+---------------------+
|
register / record / query
|
+---------------------+ +------------------------+
| Provenance API |---->| Provenance.Store |
| (lib/provenance.ex) | | (ETS GenServer) |
+---------------------+ | |
| :provenance_artifacts |
+---------------------+ | :provenance_edges |
| CompileTracer |---->| :provenance_modules |
| (compile-time hook) | +------------------------+
+---------------------+
Process Dictionary
+---------------------+
| put_context/1 | Per-process provenance context
| get_context/0 | (:provenance_context key)
+---------------------+- Provenance -- Public API. Delegates to
Provenance.Storefor persistence and provides ID builder functions. - Provenance.Store -- GenServer owning three ETS tables: artifacts (set), edges (duplicate_bag, bidirectional), and modules (set).
- Provenance.CompileTracer -- Elixir compiler tracer. On each compiled module, registers it with layer classification and records remote function call edges.
- Process context -- Stored in the process dictionary. Designed for inclusion in provenance recordings during request/job execution.
Ecto Integration (Planned)
Ecto is an optional dependency. Current support is limited to detecting Ecto schemas in the compile tracer for layer classification. Future versions will add:
- Automatic query-level provenance via Ecto telemetry
- Schema field to artifact ID mapping
- Migration tracking
API Reference
Core
| Function | Description |
|---|---|
Provenance.register_module(module, opts) | Register a module with metadata and dependencies |
Provenance.record(artifact_id, opts) |
Record provenance (:origin option for producer) |
Provenance.lookup(artifact_id) | Look up artifact info |
Provenance.related(artifact_id) | List related artifact IDs |
Provenance.module_graph() | Full module dependency graph |
ID Builders
| Function | Description |
|---|---|
Provenance.module_id(module) | "mod:Module.Name" |
Provenance.function_id(mod, fun, arity) | "fn:Mod.fun/arity" |
Provenance.table_id(name) | "tbl:name" |
Provenance.record_id(table, pk) | "rec:table:pk" |
Process Context
| Function | Description |
|---|---|
Provenance.put_context(keyword) | Merge attrs into process context |
Provenance.get_context() | Get current process context map |
License
MIT -- see LICENSE.