Context Propagation

Copy Markdown View Source

Rulestead keeps context propagation explicit.

%Rulestead.Context{} is built at the host boundary, passed forward intentionally, and restored only from bounded serialized fields. There is no ambient process-dictionary lookup, no hidden cross-process mutation, and no raw Phoenix or Oban structs in telemetry or job payloads.

Plug to Phoenix

Use Rulestead.Plug in the request pipeline to assign a normalized context onto the conn:

plug Rulestead.Plug,
  actor: {:assign, :current_user},
  environment: "prod",
  request_id: {:header, "x-request-id"},
  session_id: {:session, "session_id"},
  targeting_key_sources: [
    {:session, "targeting_key"},
    {:cookie, "rulestead_targeting_key"},
    {:header, "x-rulestead-targeting-key"}
  ]

The plug writes only conn.assigns[:rulestead_context].

If you need the context directly, call Rulestead.Phoenix.context_from_conn/2 with the same explicit sources. The contract is "project a bounded context from request data", not "make the conn itself globally available."

Phoenix to LiveView

Pass the bounded values you need during mount, then rebuild the context from socket assigns and explicit session data:

def mount(_params, session, socket) do
  socket =
    assign(socket,
      rulestead_context: socket.assigns[:rulestead_context],
      current_user: socket.assigns[:current_user]
    )

  {:ok,
   Rulestead.LiveView.assign_flags(
     socket,
     %{checkout_enabled: "checkout-redesign"},
     session: session
   )}
end

Rulestead.LiveView.assign_flags/3 resolves flags through the keyed runtime layer only. It does not read the store, runtime cache internals, or the pure payload-first evaluator directly.

If you only need to carry context without assigning flags yet, use Rulestead.LiveView.context_from_socket/2 and pass the resulting %Rulestead.Context{} onward explicitly.

LiveView or Request to Oban

Serialize the current context explicitly when enqueueing a job:

job =
  %Oban.Job{args: %{"task" => "sync"}}
  |> Rulestead.Oban.Middleware.attach(context: socket.assigns.rulestead_context)

Inside a worker:

defmodule MyApp.SyncWorker do
  use Rulestead.Oban.Worker
end

context = MyApp.SyncWorker.rulestead_context(job)

The Oban seam serializes only bounded context fields and restores them with Rulestead.Oban.context_from_job/1.

What "bounded" means

The supported propagation payload is limited to context fields such as:

  • actor
  • targeting_key
  • tenant_key
  • environment
  • attributes
  • request_id
  • session_id
  • strict?

That keeps propagation auditable and keeps framework structs, socket state, and arbitrary request baggage out of telemetry and job payloads.

Supported Chain

The supported propagation path is:

  1. Build or assign %Rulestead.Context{} from request input with Rulestead.Plug or Rulestead.Phoenix.context_from_conn/2.
  2. Rebuild or reuse that context in LiveView with Rulestead.LiveView.context_from_socket/2.
  3. Serialize that context into jobs with Rulestead.Oban.Middleware.attach/2.
  4. Restore it in workers with use Rulestead.Oban.Worker.

Every step is visible in application code. That is the point.

Explicitly Unsupported

  • Hidden process-dictionary propagation
  • Reading context from unrelated process state
  • Serializing raw Plug.Conn, Phoenix.LiveView.Socket, or Oban.Job structs into telemetry or job payloads
  • Bypassing the keyed runtime layer from LiveView helpers
  • Reconstructing context from private runtime cache or store internals