# Threadline ↔ Sigra integration
<!-- SIGRA-03-INTEGRATION-GUIDE -->
Use `Threadline.Integrations.Sigra` when your Phoenix host already uses Sigra for request authentication and impersonation.
## Install
Add Sigra to your host application's `mix.exs` as an optional dependency:
```elixir
{:sigra, "~> 0.2", optional: true}
```
This dependency is for hosts; never for the library.
## Plug callback wire-up
Wire `Threadline.Plug` directly with both callbacks in the router pipeline
after your host has established request auth and any proxy-aware IP rewriting:
```elixir
pipeline :api do
plug :accepts, ["json"]
plug Threadline.Plug,
actor_fn: &Threadline.Integrations.Sigra.actor_ref_from_conn/1,
context_overrides_fn: &Threadline.Integrations.Sigra.audit_context_overrides_from_conn/1
end
```
`actor_fn` decides who acted. `context_overrides_fn` can add only additive
request metadata when the baseline conn extraction has no value.
`Threadline.Plug` always derives `request_id` from `x-request-id` first and
`correlation_id` from `x-correlation-id` first. The Sigra callback is therefore
supplemental: it fills missing values and never replaces an explicit header or
already-derived actor identity. If the callback returns unknown keys or any
non-map value, `Threadline.Plug` raises `ArgumentError` immediately.
Hosts still own transport normalization. If your deployment needs proxy-aware IP
handling, rewrite `conn.remote_ip` upstream before `Threadline.Plug` runs.
## Behaviors locked by SPEC
1. Impersonation maps to `:admin`. When `current_scope.impersonating_from` is non-nil, `actor_ref_from_conn/1` returns an admin actor and keeps the impersonated user encoded in correlation metadata.
2. API token maps to `:service_account`. When `current_scope.auth_method` is `:api_token` or `:jwt`, `actor_ref_from_conn/1` returns a service account actor using `current_scope.id`.
3. Active organization adds a suffix. When Sigra exposes an active organization, the adapter appends `:org:<id>` to the derived correlation id.
4. Anonymous / Sigra-absent returns `nil`. If the request has no supported Sigra actor shape, `actor_ref_from_conn/1` returns raw `nil`.
5. `x-correlation-id` header always wins. When the header is present, `audit_context_overrides_from_conn/1` returns `%{}` so `Threadline.Plug` preserves the request value instead of replacing it.
6. `x-request-id` and any existing actor identity also stay authoritative. `context_overrides_fn` is additive request metadata only; it is not a second actor path.
7. Plug-only adapter; no telemetry subscription in v1.
## correlation_id formats
- Impersonation: `sigra-imp:<session_id>:user:<imp_user_id>`
- Plain session: `sigra-session:<session_id>`
- API token: `sigra-token:<token_id>`
- Anonymous / Sigra absent: no override / `%{}`
## Soft-dep contract
`Code.ensure_loaded?(Sigra.Session)` is the single soft-dependency gate.
When that check is false:
- `actor_ref_from_conn/1` returns `nil`
- `audit_context_overrides_from_conn/1` returns `%{}`