Context for finding queries and lifecycle operations.
Read-path: strategies query active findings during init/2.
Write-path: post-converge persists raise/update/clear actions.
Subject queries use denormalized subject_kind and subject_id columns
instead of JSON operators for SQL Server 2017 compatibility.
Upserts use transactions (read-then-write) instead of ON CONFLICT
for SQL Server compatibility.
Summary
Functions
Query active findings with flexible filters.
Mode-aware query: automatically prefixes :actor and :finding_key filters
when the episode is a dry run with persist_findings enabled.
Walks the causal chain upward from a finding, returning ancestors.
Returns active child findings caused by the given finding key.
Count active findings matching the given filters. Accepts same opts as active_for/2.
Persist a finding action from a ConvergeResult.
Project a single %Finding{} into a compact, JSON-safe summary map.
Project findings into compact, JSON-safe maps for strategy context assembly.
Returns true if an active finding with the given key was updated within window_ms milliseconds.
Returns the root cause finding for the given finding key.
Functions
Query active findings with flexible filters.
Examples
Cyclium.Findings.active_for(actor: :po_status, class: "po_stalled")
Cyclium.Findings.active_for(subject: %{kind: "po", id: "PO-1955"})
Cyclium.Findings.active_for(subject: %{kind: "resource"})
Cyclium.Findings.active_for(finding_key: "po_stalled:PO-1955")
Cyclium.Findings.active_for(class: "non_responsive")An unrecognized filter key raises ArgumentError rather than being silently
dropped — a dropped filter returns a superset of what was asked for. Reserved
option keys (:limit, :offset, :order_by, :env, :exclude_archived) are
the exception: they are accepted in the filter list too, so
active_for(finding_key: k, limit: 1) works despite Elixir folding the
trailing keywords into one argument.
Options
:limit— max rows to return:offset— rows to skip (default: 0):order_by— override the default[desc: :updated_at]ordering. Accepts an allow-listed column (:updated_at,:raised_at,:inserted_at), optionally as{dir, column}(e.g.{:desc, :raised_at}).raised_atis the better signal for context: an in-place update bumpsupdated_atwithout a new run, so "most recently touched" and "most recently found" diverge.:exclude_archived— whentrue, excludes findings with a non-nilarchived_at(default:false):env— override the env scope (defaults toCyclium.Env.current()). Pass a string to read another env's findings (demo/staging inspection or backfill), ornilto target the unset/default env explicitly.
Mode-aware query: automatically prefixes :actor and :finding_key filters
when the episode is a dry run with persist_findings enabled.
In live mode (or dry runs without persist_findings), behaves identically to active_for/2.
In dry run mode with a persist prefix, rewrites filter keys so strategies can
transparently read their own dry run findings.
Examples
# In a strategy's init/2, pass the episode to get mode-aware results:
Cyclium.Findings.active_for_mode([actor: "po_monitor"], episode)
# Dry run with persist_findings: true → queries actor: "dry_run:po_monitor"
# Live mode → queries actor: "po_monitor" (unchanged)
Walks the causal chain upward from a finding, returning ancestors.
Starting from the given finding, follows caused_by_key links up
to max_depth levels. Returns a list of findings from immediate parent
to root (oldest ancestor).
Options
:max_depth— maximum chain depth (default: 10)
Returns active child findings caused by the given finding key.
Count active findings matching the given filters. Accepts same opts as active_for/2.
Persist a finding action from a ConvergeResult.
Actions
{:raise, params}— upsert active finding (last writer wins on mutable fields){:update, key, changes}— update mutable fields on an active finding{:clear, key}— idempotent clear (set status to:cleared){:clear, key, reason}— clear with reason stored in evidence_refs
Project a single %Finding{} into a compact, JSON-safe summary map.
Options
:summary_limit— maxsummarylength in graphemes (default500); longer summaries are truncated with an…suffix. Passnilto disable truncation.
Project findings into compact, JSON-safe maps for strategy context assembly.
Strategies read active findings during context assembly, and that payload is
journaled by the episode runner (JSON-encoded for SQL Server via the Tds
adapter). Raw %Finding{} Ecto structs are not Jason.Encoder-able —
they carry __meta__ and association fields — so handing them to a strategy
crashes the episode at its first journal write the moment the actor has any
active finding. Always project findings through this before putting them in a
strategy's context.
The projection is also better prompt context: one summary-shaped line per
finding instead of every finding's full evidence_refs narrative (which can
run to multiple KB and would otherwise ride into every episode's first prompt
and journal row).
Accepts the same opts as project_finding/2 (e.g. :summary_limit).
Returns true if an active finding with the given key was updated within window_ms milliseconds.
Useful for per-subject dedup in strategies — skip re-runs when a recent finding already exists.
Examples
Cyclium.Findings.recent?("client:advisor:123", :timer.minutes(5))
Returns the root cause finding for the given finding key.
Walks up the causal chain and returns the first finding with no caused_by_key.
Returns nil if the finding itself is not found.