Packages
AI agent framework for Elixir built on OTP. TEA-based agents with crash isolation, inter-agent messaging, team supervision, and real SSE streaming to Anthropic, OpenAI, Ollama, and more.
Current section
Files
Jump to
Current section
Files
lib/raxol/agent/authorization/labels.ex
defmodule Raxol.Agent.Authorization.Labels do
@moduledoc """
Label state for the authorization engine, with monotonic merge.
Labels are a plain map that policies read (to gate on) and write (verdict
`writes`). When several policies write the SAME key, a monotonic label keeps the
MOST-RESTRICTIVE value rather than last-write-wins -- the CRDT-flavored
`_merge_monotonic_writes` from omnigent. This guarantees a stricter policy can
never be loosened by a later, more permissive one in the same pass.
A monotonic spec maps a label key to an ordering, least-restrictive first:
%{access: [:read, :write, :admin], risk: [:low, :medium, :high]}
Keys not in the spec merge last-write-wins.
"""
@type labels :: map()
@type monotonic :: %{optional(term()) => [term()]}
@doc """
Merge `writes` into `labels`. Monotonic keys keep the most-restrictive value;
other keys take the written value.
"""
@spec merge(labels(), map(), monotonic()) :: labels()
def merge(labels, writes, monotonic \\ %{}) do
Enum.reduce(writes, labels, fn {key, value}, acc ->
case Map.fetch(monotonic, key) do
{:ok, ordering} -> Map.put(acc, key, most_restrictive(Map.get(acc, key), value, ordering))
:error -> Map.put(acc, key, value)
end
end)
end
# The value with the higher rank in the ordering wins; the incoming value wins
# ties so an equally-ranked rewrite is idempotent.
defp most_restrictive(nil, value, _ordering), do: value
defp most_restrictive(current, value, ordering) do
if rank(value, ordering) >= rank(current, ordering), do: value, else: current
end
defp rank(value, ordering) do
case Enum.find_index(ordering, &(&1 == value)) do
nil -> -1
index -> index
end
end
end