Threadline

CIHex.pmHexDocsCI: Runs on GitHub Actions.

Auditing for Phoenix.

Threadline is an open-source audit library for Elixir teams using Phoenix, Ecto, and PostgreSQL. It combines PostgreSQL trigger capture with semantic actions, then exposes the audit trail through Threadline.Plug, Threadline.record_action/2, Threadline.history/3, Threadline.timeline/2, Threadline.timeline_page/2, Threadline.incident_bundle/2, Threadline.export_json/2, and Threadline.as_of/4.

Use it when you want the audit layer in your app, not a separate event system or a black box.

Start here

What you get

Quick Start

  1. Add threadline to your dependencies:

    def deps do
      [
        {:threadline, "~> 0.5"}
      ]
    end
  2. Install and migrate:

    mix threadline.install
    mix ecto.migrate
  3. Register triggers for the tables you want to audit:

    mix threadline.gen.triggers --tables users,posts,comments
    mix ecto.migrate
  4. Add the plug and set an actor inside your transaction:

     # lib/my_app_web/router.ex
     pipeline :browser do
       plug Threadline.Plug, actor_fn: &MyApp.Auth.to_actor_ref/1
     end
    
    alias Threadline.Semantics.ActorRef
    
    actor_ref = ActorRef.user("user:123")
    json = ActorRef.to_map(actor_ref) |> Jason.encode!()
    
     MyApp.Repo.transaction(fn ->
       MyApp.Repo.query!("SELECT set_config('threadline.actor_ref', $1::text, true)", [json])
       MyApp.Repo.insert!(%MyApp.Post{title: "Ship audit logs"})
     end)
    
     {:ok, _action} =
       Threadline.record_action(:post_published,
         repo: MyApp.Repo,
         actor: actor_ref
       )
  5. Query the audit trail:

     Threadline.history(MyApp.Post, post.id, repo: MyApp.Repo)
     Threadline.timeline([table: "posts"], repo: MyApp.Repo)
     Threadline.timeline_page([table: "posts"], repo: MyApp.Repo, page_size: 200)
     Threadline.export_json([table: "posts"], repo: MyApp.Repo)
     Threadline.as_of(MyApp.Post, post.id, DateTime.utc_now(), repo: MyApp.Repo)

Use Threadline.timeline/2 for smaller eager slices. When the window is too large to read eagerly, switch to Threadline.timeline_page/2 and continue with next_cursor instead of offset pagination.

Keep the root README as the map, then use guides/domain-reference.md for the canonical "which public API first?" table, guides/getting-started-saas.md for the canonical first-hour Phoenix walkthrough, and guides/incident-playbook.md for operator recipes.

Operator Surface

Threadline provides an optional, drop-in LiveView UI to investigate the audit trail natively in your app, including a polled coverage dashboard at /audit/coverage, a read-only redaction drift viewer at /audit/policy/redaction, and parity Mix tasks mix threadline.health.coverage plus mix threadline.policy.show. Ensure you have the optional Phoenix surface dependencies declared in mix.exs. Threadline stays in-tree for now rather than splitting the UI into a separate package; keep the closeout rationale and exact current proof pins in guides/upgrade-path.md rather than copying them into generic install docs.

1-Minute Mount

defmodule MyAppWeb.Router do
  use MyAppWeb, :router
  import Threadline.OperatorSurface.Router

  # Must pipe through your own authentication
  scope "/audit", MyAppWeb do
    pipe_through [:browser, :require_authenticated_admin]

    threadline_operator_surface "/",
      actor_fn: &MyApp.Audit.current_actor/1,
      authorize_fn: &MyApp.Audit.authorize_operator/1
  end
end

For the full first-hour mounted walkthrough, read guides/getting-started-saas.md. For the "fail-closed" security default, authorization setup, and screen inventory, read the Operator Surface guide. For the broader host and framework contract across Threadline.Plug, Threadline.Job, Threadline.Integrations.*, and operator-surface auth/export auth, read guides/integration-contracts.md. For the current support claims, stay with guides/upgrade-path.md rather than inferring broader compatibility from the README.

Notes

Documentation