Threadline Integration

Copy Markdown View Source

This guide is the canonical adoption path for composing Chimeway with Threadline audit telemetry. Threadline integration is attach-only: add the optional dependency and attach the reporter in Application.start/2 — there are no Chimeway migrations to run, no notifier to author, and no host-triggered events. The reporter translates Chimeway notification lifecycle telemetry into Threadline's immutable audit ledger automatically.

Responsibility split (SEED-003)

Chimeway orchestrates the when and why: durable notification lifecycle, suppression and preference gates, idempotency, explainability, and operator traces you can search at /admin/chimeway.

Threadline owns audit ledger: immutable, append-only event records. Chimeway does not write to Threadline directly — the Threadline reporter listens to Chimeway lifecycle telemetry and records the outcome as a semantic audit action. Threadline emits and persists the audit row; Chimeway does not mutate Threadline records.

This integration is not a Chimeway.Adapter delivery seam — it is a :telemetry handler bridge only.

1. Dependencies

Add Chimeway and Threadline to your host mix.exs:

def deps do
  [
    {:chimeway, "~> 1.0"},
    threadline_dep()
  ]
end

defp threadline_dep do
  case System.get_env("THREADLINE_PATH") do
    nil -> {:threadline, "~> 0.7", optional: true, runtime: false}
    path -> {:threadline, path: path, optional: true, runtime: false}
  end
end

For local development and integration proof, check out the Threadline repo as a sibling and point THREADLINE_PATH at it:

THREADLINE_PATH=../threadline mix deps.get

Production adopters use {:threadline, "~> 0.7", optional: true, runtime: false} from Hex. Local proof and CI use a sibling checkout pinned to the integration ref documented in MAINTAINING.md.

2. Attach reporter

Attach the reporter once at boot in your Application.start/2:

def start(_type, _args) do
  Chimeway.Telemetry.ThreadlineReporter.attach()

  children = [
    # ... your supervision tree
  ]

  Supervisor.start_link(children, strategy: :one_for_one, name: MyApp.Supervisor)
end

The Threadline reporter attach/0 call is idempotent — calling it more than once is safe. Then configure the reporter with the Threadline repo and the actor that owns the audit rows:

config :chimeway, :threadline_reporter,
  repo: MyApp.Threadline.Repo,
  actor: Threadline.Semantics.ActorRef.new(:system, "chimeway")

Only :repo and :actor are required. No per-notification host callbacks are needed — the reporter derives everything from Chimeway lifecycle telemetry.

3. What gets recorded

The reporter listens to Chimeway notification lifecycle telemetry and records one Threadline audit action per terminal outcome:

Lifecycle outcomeThreadline action atomcorrelation_id
Notification suppressed:notification_suppressedforwarded from Chimeway trigger opts
Notification deferred:notification_deferredforwarded from Chimeway trigger opts
Notification dispatched:notification_dispatchedforwarded from Chimeway trigger opts
Notification failed:notification_failedforwarded from Chimeway trigger opts

Each audit action carries only deterministic outcome metadata (the outcome reason and a bounded summary) — never payload bodies, email contents, templates, or provider responses.

Pass correlation_id: in Chimeway.trigger/3 opts to thread the audit row through to Threadline.Query.timeline/2 strict filter. Without it, the Threadline timeline cannot isolate this notification's lifecycle in a multi-event trace.

4. Verification

Seed the demo host Threadline scenario to exercise the bridge end-to-end:

DemoHost.Seeds.seed_threadline_notification()

Then run the named proof command:

THREADLINE_PATH=../threadline mix verify.threadline

This exercises the Chimeway-root lifecycle proof and the demo host telemetry proof. After seeding, search /admin/chimeway for the notification's operator trace and confirm the matching Threadline audit row is linked by correlation_id.