# `MishkaCmsCore.Activity.Sources.Oban`
[🔗](https://github.com/mishka-group/mishka_cms_core/blob/v0.0.1/lib/mishka_cms_core/activity/sources/oban.ex#L5)

Activity source backed by Oban job telemetry. Attaches once on app boot
via `attach/0` (called from `MishkaCmsCore.Application.start/2`).

## How it works

  * Telemetry handler catches `:oban, :job, :start | :stop | :exception`
    events and rebroadcasts them on a set of PubSub topics so subscribers
    get exactly the slice they asked for (see "Topics").
  * Initial fetch reads recent rows from the `oban_jobs` table, filtered to
    the same scope.
  * Job → entry mapping treats exceptions and discards as errors,
    retryables as warnings, everything else as info.

## Scope keys read

  * `:current_user` — tenants only see their `site_id`.
  * `:resources` — optional list of resource modules. When present, only
    jobs whose `args["resource"]` matches one of them are shown (e.g. the
    Component admin page passes `[MishkaCmsCore.Runtime.Component]`).

## Topics

A job is rebroadcast on every topic that applies to it, so a subscriber
picks the most specific one for its scope:

  * `oban_activity` — all jobs (master, no resource filter)
  * `oban_activity:site:<id>` — jobs for one site
  * `oban_activity:res:<Module>` — jobs for one resource (any site)
  * `oban_activity:site:<id>:res:<Module>` — one resource on one site

## Where `site_id` / `resource` come from

`site_id` is read from either args shape:

  * top-level: `%{"site_id" => "<uuid>"}`
  * nested:    `%{"data" => %{"site_id" => "<uuid>"}}` — used by
    `MishkaCmsCore.Runtime.Workers.RuntimeCompilerWorker`.

`resource` is read from `args["resource"]` (the inspected module string set
by `MishkaCmsCore.Runtime.Resources.Changes.SendToOban`). Jobs without one
never appear on a resource-scoped topic.

See `MishkaCmsCore.Activity.Behaviours.Source` for the behaviour contract.

# `attach`

Attach Oban telemetry → PubSub bridge. Idempotent.

# `entry_from_row`

Build an entry map from a unified `MishkaCmsCore.Activity.Query` SQL row (the
DB-side merge path). The row carries `worker`, the job `state`, and `args`, so
it reuses the same event/severity/message derivation as the in-memory path.

---

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