Finding lifecycle
The internal/findinglifecycle subsystem ingests durable findings,
enforces a closed LifecycleState transition graph, and emits paired
finding_lifecycle.action slog records via a bounded
LifecycleAction enum (internal/findinglifecycle/lifecycle_emit.go).
Records are written to slog and a process-scoped ring buffer
(capacity 16) so operators and agents can inspect recent activity
without scraping logs.
Bounded actions
Section titled “Bounded actions”The full enum lives in internal/findinglifecycle/lifecycle_emit.go.
Adding an action requires updating severityForLifecycleAction, the
exhaustive enum test, and the ### Finding lifecycle recipe block in
docs/topics/agent/tracing.md.
| Action | Severity | Meaning |
|---|---|---|
ingest_skipped | info | Diagnostic batch was non-authoritative; no rows ingested. |
ingest_failed | warn | Ingest pipeline failed (validation or store). |
ingest_scope_failed | warn | Workspace scope resolution failed. |
ingest_pool_failed | warn | Worker-pool start failed; ingest disabled. |
ingest_worker_submit_failed | warn | Worker-pool submit rejected the ingest job. |
link_persist_failed | warn | Cross-finding link batch failed to persist. |
validation_findings_ingested | info | Verify pass ingested operation findings. |
validation_findings_resolved | info | Verify pass resolved one or more findings. |
finding_lifecycle.action records carry feature=finding_lifecycle,
the bounded action enum, and metadata-only attributes
(session_id, finding_count, lifecycle_status, reason, error,
debounced, count). Bodies and diagnostic source ranges never
enter the ring.
Lifecycle states
Section titled “Lifecycle states”The closed state machine lives in internal/findinglifecycle/types.go:
| State | Meaning |
|---|---|
open | Default on first ingest; visible to read paths. |
resolved | Closed verdict; remains visible. |
suppressed | Hidden from the default read path. |
accepted_risk | Visible with an explicit risk badge. |
Illegal transitions are rejected at ApplyTransition; the graph is
exhaustively property-tested
(internal/findinglifecycle/transition_graph_test.go).
Operator surfaces
Section titled “Operator surfaces”/findings (TUI)
Section titled “/findings (TUI)”Aliases: /finding-lifecycle, /finding_lifecycle. Read-only.
Renders ingest counters (InFlight, FailureCount, last success /
failure timestamps), the most-recent ring entries, and a one-line
summary the sidebar reuses.
bmo config show-finding-lifecycle (CLI)
Section titled “bmo config show-finding-lifecycle (CLI)”Same snapshot as /findings for the current process. CLI and TUI
share the ring only when run in the same process (the ring is
process-global, not persisted). A regression test enforces byte-equal
JSON parity between the CLI and the App-level snapshot
(internal/cmd/config_cmd_show_finding_lifecycle_test.go).
Sidebar
Section titled “Sidebar”The TUI sidebar shows Findings: with one of:
- empty — no in-flight ingest and no recent warn/error events.
ingest x<n>—<n>ingest passes are currently in flight.warn: <action>— the most recent ring entry was warn or error severity.
Permissive session filter: events without a session id are included alongside the current session, so cross-session worker failures still surface.
Agent tool
Section titled “Agent tool”list_recent_finding_lifecycle_events returns JSON
{ "ring_capacity", "returned", "events" } with snake_case event
fields. Optional session_id filter and limit (1..16, default
ring capacity). The same data drives /findings and
bmo config show-finding-lifecycle.
list_findings (read-only, ExposeFirst) lists the per-session
finding rows the read path exposes after the trust matrix filter.
MCP tool twins
Section titled “MCP tool twins”bmo_list_findings and bmo_list_recent_finding_lifecycle_events
expose the same JSON contracts to MCP-host agents (Cursor, Claude
Desktop, etc.). Parity with the in-process agent tools is enforced by
internal/mcp/tools/findings_test.go.
Failure modes the lane catches
Section titled “Failure modes the lane catches”- Pool start failure on app boot.
ingest_pool_failed(Warn) at startup; subsequent ingest calls become no-ops. The sidebar carries the warn label until the next session start. - Cross-finding link persistence failure.
link_persist_failed(Warn) when a batch flush hits the store; ingest continues for new findings. - Diagnostic ingest failed mid-flight.
ingest_failed(Warn) withsession_id,finding_count,debounced,error. Pair the precedingingest_skipped(Info) records bysession_idto see whether the same session was previously short-circuited by the trust gate.
Privacy posture
Section titled “Privacy posture”Lifecycle records carry only metadata. Specifically out of scope:
- Finding body text and operator notes
- Diagnostic source-range contents
- File paths beyond the workspace-scoped session id
The ring truncates error_summary and lifecycle_status to keep
cardinality predictable. If a future arm needs a structured signal,
add it as a new LifecycleAction enum constant — do not stuff data
into free-text attributes.