phase0: add app skeleton, config cache, and precommit alias

This commit is contained in:
2026-03-13 18:56:23 +01:00
parent cc78558612
commit 5e478cd305
18 changed files with 281 additions and 5 deletions

View File

@@ -4,10 +4,10 @@ Implementation checklist for Parrhesia relay.
## Phase 0 — foundation
- [ ] Confirm architecture doc with final NIP scope (`docs/ARCH.md`)
- [ ] Add core deps (websocket/http server, ecto_sql/postgrex, telemetry, test tooling)
- [ ] Establish application config structure (limits, policies, feature flags)
- [ ] Wire initial supervision tree skeleton
- [x] Confirm architecture doc with final NIP scope (`docs/ARCH.md`)
- [x] Add core deps (websocket/http server, ecto_sql/postgrex, telemetry, test tooling)
- [x] Establish application config structure (limits, policies, feature flags)
- [x] Wire initial supervision tree skeleton
## Phase 1 — protocol core (NIP-01)

25
config/config.exs Normal file
View File

@@ -0,0 +1,25 @@
import Config
config :parrhesia,
limits: [
max_frame_bytes: 1_048_576,
max_event_bytes: 262_144,
max_filters_per_req: 16,
max_subscriptions_per_connection: 32
],
policies: [
auth_required_for_writes: false,
auth_required_for_reads: false,
min_pow_difficulty: 0,
accept_ephemeral_events: true
],
features: [
nip_45_count: true,
nip_50_search: false,
nip_77_negentropy: false,
nip_ee_mls: false
]
config :parrhesia, Parrhesia.Web.Endpoint, port: 4000
import_config "#{config_env()}.exs"

3
config/dev.exs Normal file
View File

@@ -0,0 +1,3 @@
import Config
config :logger, :console, format: "[$level] $message\n"

1
config/prod.exs Normal file
View File

@@ -0,0 +1 @@
import Config

3
config/test.exs Normal file
View File

@@ -0,0 +1,3 @@
import Config
config :logger, level: :warning

View File

@@ -0,0 +1,22 @@
defmodule Parrhesia.Application do
@moduledoc false
use Application
@impl true
def start(_type, _args) do
children = [
Parrhesia.Telemetry,
Parrhesia.Config,
Parrhesia.Storage.Supervisor,
Parrhesia.Subscriptions.Supervisor,
Parrhesia.Auth.Supervisor,
Parrhesia.Policy.Supervisor,
Parrhesia.Web.Endpoint,
Parrhesia.Tasks.Supervisor
]
opts = [strategy: :one_for_one, name: Parrhesia.Supervisor]
Supervisor.start_link(children, opts)
end
end

View File

@@ -0,0 +1,16 @@
defmodule Parrhesia.Auth.Supervisor do
@moduledoc """
Supervision entrypoint for AUTH challenge/session tracking.
"""
use Supervisor
def start_link(init_arg \\ []) do
Supervisor.start_link(__MODULE__, init_arg, name: __MODULE__)
end
@impl true
def init(_init_arg) do
Supervisor.init([], strategy: :one_for_one)
end
end

65
lib/parrhesia/config.ex Normal file
View File

@@ -0,0 +1,65 @@
defmodule Parrhesia.Config do
@moduledoc """
Runtime configuration cache backed by ETS.
"""
use GenServer
@table __MODULE__
@root_key :config
def start_link(init_arg \\ []) do
GenServer.start_link(__MODULE__, init_arg, name: __MODULE__)
end
@impl true
def init(_init_arg) do
_table = :ets.new(@table, [:named_table, :public, read_concurrency: true])
config =
:parrhesia
|> Application.get_all_env()
|> Enum.into(%{})
:ets.insert(@table, {@root_key, config})
{:ok, %{}}
end
@spec all() :: map() | keyword()
def all do
case :ets.lookup(@table, @root_key) do
[{@root_key, config}] -> config
[] -> %{}
end
end
@spec get([atom()], term()) :: term()
def get(path, default \\ nil) when is_list(path) do
case fetch(path) do
{:ok, value} -> value
:error -> default
end
end
defp fetch(path) do
Enum.reduce_while(path, {:ok, all()}, fn key, {:ok, current} ->
case fetch_key(current, key) do
{:ok, value} -> {:cont, {:ok, value}}
:error -> {:halt, :error}
end
end)
end
defp fetch_key(current, key) when is_map(current), do: Map.fetch(current, key)
defp fetch_key(current, key) when is_list(current) do
if Keyword.keyword?(current) do
Keyword.fetch(current, key)
else
:error
end
end
defp fetch_key(_current, _key), do: :error
end

View File

@@ -0,0 +1,16 @@
defmodule Parrhesia.Policy.Supervisor do
@moduledoc """
Supervision entrypoint for policy/rate-limit/ACL workers.
"""
use Supervisor
def start_link(init_arg \\ []) do
Supervisor.start_link(__MODULE__, init_arg, name: __MODULE__)
end
@impl true
def init(_init_arg) do
Supervisor.init([], strategy: :one_for_one)
end
end

View File

@@ -0,0 +1,16 @@
defmodule Parrhesia.Storage.Supervisor do
@moduledoc """
Supervision entrypoint for storage adapter processes.
"""
use Supervisor
def start_link(init_arg \\ []) do
Supervisor.start_link(__MODULE__, init_arg, name: __MODULE__)
end
@impl true
def init(_init_arg) do
Supervisor.init([], strategy: :one_for_one)
end
end

View File

@@ -0,0 +1,16 @@
defmodule Parrhesia.Subscriptions.Supervisor do
@moduledoc """
Supervision entrypoint for subscription index and fanout workers.
"""
use Supervisor
def start_link(init_arg \\ []) do
Supervisor.start_link(__MODULE__, init_arg, name: __MODULE__)
end
@impl true
def init(_init_arg) do
Supervisor.init([], strategy: :one_for_one)
end
end

View File

@@ -0,0 +1,16 @@
defmodule Parrhesia.Tasks.Supervisor do
@moduledoc """
Supervision entrypoint for background maintenance jobs.
"""
use Supervisor
def start_link(init_arg \\ []) do
Supervisor.start_link(__MODULE__, init_arg, name: __MODULE__)
end
@impl true
def init(_init_arg) do
Supervisor.init([], strategy: :one_for_one)
end
end

View File

@@ -0,0 +1,16 @@
defmodule Parrhesia.Telemetry do
@moduledoc """
Supervision entrypoint for relay telemetry workers.
"""
use Supervisor
def start_link(init_arg \\ []) do
Supervisor.start_link(__MODULE__, init_arg, name: __MODULE__)
end
@impl true
def init(_init_arg) do
Supervisor.init([], strategy: :one_for_one)
end
end

View File

@@ -0,0 +1,16 @@
defmodule Parrhesia.Web.Endpoint do
@moduledoc """
Supervision entrypoint for WS/HTTP ingress.
"""
use Supervisor
def start_link(init_arg \\ []) do
Supervisor.start_link(__MODULE__, init_arg, name: __MODULE__)
end
@impl true
def init(_init_arg) do
Supervisor.init([], strategy: :one_for_one)
end
end

15
mix.exs
View File

@@ -7,17 +7,23 @@ defmodule Parrhesia.MixProject do
version: "0.1.0",
elixir: "~> 1.19",
start_permanent: Mix.env() == :prod,
deps: deps()
deps: deps(),
aliases: aliases()
]
end
# Run "mix help compile.app" to learn about applications.
def application do
[
mod: {Parrhesia.Application, []},
extra_applications: [:logger]
]
end
def cli do
[preferred_envs: [precommit: :test]]
end
# Run "mix help deps" to learn about dependencies.
defp deps do
[
@@ -41,8 +47,15 @@ defmodule Parrhesia.MixProject do
{:websockex, "~> 0.4", only: :test},
# Project tooling
{:credo, "~> 1.7", only: [:dev, :test], runtime: false},
{:deps_changelog, "~> 0.3"},
{:igniter, "~> 0.6", only: [:dev, :test]}
]
end
defp aliases do
[
precommit: ["format --check-formatted", "credo --strict", "test"]
]
end
end

View File

@@ -1,14 +1,17 @@
%{
"bandit": {:hex, :bandit, "1.10.3", "1e5d168fa79ec8de2860d1b4d878d97d4fbbe2fdbe7b0a7d9315a4359d1d4bb9", [:mix], [{:hpax, "~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}, {:plug, "~> 1.18", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:thousand_island, "~> 1.0", [hex: :thousand_island, repo: "hexpm", optional: false]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "99a52d909c48db65ca598e1962797659e3c0f1d06e825a50c3d75b74a5e2db18"},
"bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"},
"bypass": {:hex, :bypass, "2.1.0", "909782781bf8e20ee86a9cabde36b259d44af8b9f38756173e8f5e2e1fabb9b1", [:mix], [{:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.0", [hex: :plug_cowboy, repo: "hexpm", optional: false]}, {:ranch, "~> 1.3", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "d9b5df8fa5b7a6efa08384e9bbecfe4ce61c77d28a4282f79e02f1ef78d96b80"},
"cowboy": {:hex, :cowboy, "2.14.2", "4008be1df6ade45e4f2a4e9e2d22b36d0b5aba4e20b0a0d7049e28d124e34847", [:make, :rebar3], [{:cowlib, ">= 2.16.0 and < 3.0.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, ">= 1.8.0 and < 3.0.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "569081da046e7b41b5df36aa359be71a0c8874e5b9cff6f747073fc57baf1ab9"},
"cowboy_telemetry": {:hex, :cowboy_telemetry, "0.4.0", "f239f68b588efa7707abce16a84d0d2acf3a0f50571f8bb7f56a15865aae820c", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7d98bac1ee4565d31b62d59f8823dfd8356a169e7fcbb83831b8a5397404c9de"},
"cowlib": {:hex, :cowlib, "2.16.0", "54592074ebbbb92ee4746c8a8846e5605052f29309d3a873468d76cdf932076f", [:make, :rebar3], [], "hexpm", "7f478d80d66b747344f0ea7708c187645cfcc08b11aa424632f78e25bf05db51"},
"credo": {:hex, :credo, "1.7.17", "f92b6aa5b26301eaa5a35e4d48ebf5aa1e7094ac00ae38f87086c562caf8a22f", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "1eb5645c835f0b6c9b5410f94b5a185057bcf6d62a9c2b476da971cde8749645"},
"db_connection": {:hex, :db_connection, "2.9.0", "a6a97c5c958a2d7091a58a9be40caf41ab496b0701d21e1d1abff3fa27a7f371", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "17d502eacaf61829db98facf6f20808ed33da6ccf495354a41e64fe42f9c509c"},
"decimal": {:hex, :decimal, "2.3.0", "3ad6255aa77b4a3c4f818171b12d237500e63525c2fd056699967a3e7ea20f62", [:mix], [], "hexpm", "a4d66355cb29cb47c3cf30e71329e58361cfcb37c34235ef3bf1d7bf3773aeac"},
"deps_changelog": {:hex, :deps_changelog, "0.3.5", "65981997d9bc893b8027a0c03da093a4083328c00b17f562df269c2b61d44073", [:mix], [], "hexpm", "298fcd7794395d8e61dba8d29ce8fcee09f1df4d48adb273a41e8f4a1736491e"},
"ecto": {:hex, :ecto, "3.13.5", "9d4a69700183f33bf97208294768e561f5c7f1ecf417e0fa1006e4a91713a834", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "df9efebf70cf94142739ba357499661ef5dbb559ef902b68ea1f3c1fabce36de"},
"ecto_sql": {:hex, :ecto_sql, "3.13.5", "2f8282b2ad97bf0f0d3217ea0a6fff320ead9e2f8770f810141189d182dc304e", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.13.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.7", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.19 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "aa36751f4e6a2b56ae79efb0e088042e010ff4935fc8684e74c23b1f49e25fdc"},
"file_system": {:hex, :file_system, "1.1.1", "31864f4685b0148f25bd3fbef2b1228457c0c89024ad67f7a81a3ffbc0bbad3a", [:mix], [], "hexpm", "7a15ff97dfe526aeefb090a7a9d3d03aa907e100e262a0f8f7746b78f8f87a5d"},
"finch": {:hex, :finch, "0.21.0", "b1c3b2d48af02d0c66d2a9ebfb5622be5c5ecd62937cf79a88a7f98d48a8290c", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.6.2 or ~> 1.7", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 1.1", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "87dc6e169794cb2570f75841a19da99cfde834249568f2a5b121b809588a4377"},
"glob_ex": {:hex, :glob_ex, "0.1.11", "cb50d3f1ef53f6ca04d6252c7fde09fd7a1cf63387714fe96f340a1349e62c93", [:mix], [], "hexpm", "342729363056e3145e61766b416769984c329e4378f1d558b63e341020525de4"},
"hpax": {:hex, :hpax, "1.0.3", "ed67ef51ad4df91e75cc6a1494f851850c0bd98ebc0be6e81b026e765ee535aa", [:mix], [], "hexpm", "8eab6e1cfa8d5918c2ce4ba43588e894af35dbd8e91e6e55c817bca5847df34a"},

View File

@@ -0,0 +1,15 @@
defmodule Parrhesia.ApplicationTest do
use ExUnit.Case, async: false
test "starts the core supervision tree" do
assert is_pid(Process.whereis(Parrhesia.Supervisor))
assert is_pid(Process.whereis(Parrhesia.Telemetry))
assert is_pid(Process.whereis(Parrhesia.Config))
assert is_pid(Process.whereis(Parrhesia.Storage.Supervisor))
assert is_pid(Process.whereis(Parrhesia.Subscriptions.Supervisor))
assert is_pid(Process.whereis(Parrhesia.Auth.Supervisor))
assert is_pid(Process.whereis(Parrhesia.Policy.Supervisor))
assert is_pid(Process.whereis(Parrhesia.Web.Endpoint))
assert is_pid(Process.whereis(Parrhesia.Tasks.Supervisor))
end
end

View File

@@ -0,0 +1,14 @@
defmodule Parrhesia.ConfigTest do
use ExUnit.Case, async: true
test "returns configured relay limits/policies/features" do
assert Parrhesia.Config.get([:limits, :max_frame_bytes]) == 1_048_576
assert Parrhesia.Config.get([:limits, :max_event_bytes]) == 262_144
assert Parrhesia.Config.get([:policies, :auth_required_for_writes]) == false
assert Parrhesia.Config.get([:features, :nip_ee_mls]) == false
end
test "returns default for unknown keys" do
assert Parrhesia.Config.get([:limits, :unknown_limit], :missing) == :missing
end
end