# `Sigra.Plug.PutActiveOrganization`
[🔗](https://github.com/sztheory/sigra/blob/v1.20.0/lib/sigra/plug/put_active_organization.ex#L1)

The **single** authoritative write site for "set the active organization".

Every Phase 14+ call site that needs to set, clear, or change the active
organization funnels through this function: login (Plan 03), the switcher
controller (Phase 16), invitation accept (Phase 17), stale-pointer recovery
inside `Sigra.Plug.LoadActiveOrganization`, and the backfill upgrade
(Phase 18). No ad-hoc Plug-session writes. No direct ecto updates on
`user_sessions`. No shortcuts.

This function is **not** a Plug `call/2` — it is a function-call contract
invoked from controllers, other plugs, and the login entry point. It takes
a `Plug.Conn`, an `Organization` struct (or `nil` to clear), and an opts
keyword list, and returns `{:ok, updated_conn}` or `{:error, reason}`.

## Writes performed

  1. `user_sessions.active_organization_id` column (via the configured
     `Sigra.SessionStore` implementation's `update_active_organization/3`
     callback).
  2. `conn.private[:sigra_session]` (refreshed with the updated struct).
  3. `conn.assigns[:current_scope]` (via the host scope module's
     `put_active_organization/3` — D-15).

## Writes explicitly NOT performed

  * Plug session cookie writes — no mirror of the DB column into the
    cookie session (D-03/D-17).
  * Session token rotation / session renewal — a scope transition is
    NOT a trust transition (D-18).

## Authorization (T-14-06)

Before any write, `call/3` verifies the user's membership in the target
organization via `Sigra.Organizations.get_membership/3`. If `nil`, the
function returns `{:error, :not_a_member}` **without** calling the
SessionStore's `update_active_organization/3` callback. The SessionStore
callback itself does not enforce authz (see Phase 14 T-14-04).

## Options

  * `:organizations` — required. The host's `use Sigra.Organizations`
    module exposing `__sigra_org_config__/0`.
  * `:session_store` — required. Module implementing `Sigra.SessionStore`.
  * `:scope_module` — required. The host scope module whose
    `put_active_organization/3` builds the updated scope struct (D-15).
  * `:session_store_opts` — optional keyword list forwarded to the
    session store callback (e.g. `[repo: MyApp.Repo]`). Defaults to `[]`.

# `call_error`

```elixir
@type call_error() :: :not_a_member | :no_session | :no_scope | term()
```

# `call`
*since 0.8.0* 

```elixir
@spec call(Plug.Conn.t(), struct() | nil, keyword()) ::
  {:ok, Plug.Conn.t()} | {:error, call_error()}
```

Set, change, or clear the active organization on the caller's session.
Returns `{:ok, updated_conn}` on success or `{:error, reason}` on failure.

Returns `{:error, :no_session}` if no `%Sigra.Session{}` has been stashed
at `conn.private[:sigra_session]` (the FetchSession plug has not run, or
the user is not logged in). Returns `{:error, :no_scope}` if
`conn.assigns[:current_scope]` is missing or has a nil `:user`. Both
errors are fail-closed: the request does not crash and no write is
performed.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
