Current section
Files
Jump to
Current section
Files
lib/mix/tasks/scrypath.namespace_fence.ex
defmodule Mix.Tasks.Scrypath.NamespaceFence do
@moduledoc """
Fails when Sigra references leak outside the allowed integration namespace.
The fence scans the repo-owned `lib/scrypath/`, `scrypath_ops/lib/`, and
`scrypath_ops/test/` trees. Only the Sigra integration namespace and the
bounded OPSUI LiveView surfaces that consume that integration are allowed to
contain `Sigra.` references inside `scrypath_ops`.
"""
use Mix.Task
@shortdoc "Fails on forbidden Sigra references outside the integration namespace"
@pattern "Sigra."
@fence_rules [
{"lib/scrypath/", []},
{"scrypath_ops/lib/",
[
"scrypath_ops/lib/scrypath_ops/integrations/sigra/",
"scrypath_ops/lib/scrypath_ops_web/live/"
]},
{"scrypath_ops/test/",
[
"scrypath_ops/test/scrypath_ops/integrations/sigra/",
"scrypath_ops/test/scrypath_ops_web/live/"
]}
]
@impl true
def run(args) do
Mix.Task.run("app.start")
ensure_no_args!(args)
case check(File.cwd!()) do
{:ok, _summary} ->
Mix.shell().info("==> Namespace fence passed")
:ok
{:error, violations} ->
Mix.raise(format_violations(violations))
end
end
@doc """
Pure fence evaluation for a repository root.
Returns `{:ok, summary}` when no forbidden Sigra references are found and
`{:error, violations}` when the fence is tripped.
"""
@spec check(Path.t()) :: {:ok, map()} | {:error, [String.t()]}
def check(root) when is_binary(root) do
root = Path.expand(root)
violations =
@fence_rules
|> Enum.flat_map(fn {scan_dir, allowed_prefixes} ->
scan_root = Path.join(root, scan_dir)
scan_root
|> scan_files()
|> Enum.flat_map(&collect_violations(&1, root, allowed_prefixes))
end)
case violations do
[] -> {:ok, %{root: root, scanned_dirs: Enum.map(@fence_rules, &elem(&1, 0))}}
_ -> {:error, Enum.sort(violations)}
end
end
defp scan_files(scan_root) do
if File.dir?(scan_root) do
scan_root
|> Path.join("**/*")
|> Path.wildcard()
|> Enum.filter(&File.regular?/1)
else
[]
end
end
defp collect_violations(path, root, allowed_prefixes) do
rel_path = Path.relative_to(path, root)
if allowed_path?(rel_path, allowed_prefixes) do
[]
else
path
|> File.stream!(:line, [])
|> Enum.with_index(1)
|> Enum.flat_map(fn {line, line_no} ->
if String.contains?(line, @pattern) do
["#{rel_path}:#{line_no}: #{String.trim_trailing(line)}"]
else
[]
end
end)
end
end
defp allowed_path?(relative_path, allowed_prefixes) do
Enum.any?(allowed_prefixes, &String.starts_with?(relative_path, &1))
end
defp format_violations(violations) do
"""
Namespace fence failed.
Forbidden Sigra references were found outside the allowed integration namespace:
#{Enum.map_join(violations, "\n", &(" - " <> &1))}
"""
end
defp ensure_no_args!([]), do: :ok
defp ensure_no_args!(args) do
Mix.raise("scrypath.namespace_fence does not accept arguments, got: #{Enum.join(args, " ")}")
end
end