outcome

package
v0.4.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 2, 2026 License: Apache-2.0 Imports: 7 Imported by: 0

Documentation

Overview

Package outcome records, in an append-only JSONL ledger, whether an investigated incident later resolved and which answer was used for it — the "did it actually work?" signal the learning loop reads. The ledger keeps an in-memory index of still-open incidents, rebuilt by replaying the file on startup so a resolve survives a restart / leader failover.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Aggregate

type Aggregate struct {
	Recalls       int
	Resolved      int
	LastConfirmed time.Time
}

Aggregate is a per-entry roll-up of recall episodes: how often the entry was recalled, how often the incident then resolved, and when it last resolved.

type Episode

type Episode struct {
	Kind, Entry, Title, Resource string
	DupFingerprint               string // curator dedup fingerprint; stable join key for the curated-PR resolution check
	OpenedAt, ResolvedAt         time.Time
	Duration                     time.Duration
	Resolved                     bool
}

Episode is a matched open→resolve pair (or, from Episodes(), an unresolved open when Resolved is false).

type Event

type Event struct {
	Event          string `json:"event"`                     // "open" | "resolve"
	Fingerprint    string `json:"fingerprint"`               // Alertmanager fingerprint (stable firing↔resolved)
	DupFingerprint string `json:"dup_fingerprint,omitempty"` // curator dedup fingerprint (resource+cause); the curated-PR resolution join key

	Kind     string    `json:"kind,omitempty"`  // open: "recall" | "fresh"
	Entry    string    `json:"entry,omitempty"` // open+recall: the recalled entry path
	Title    string    `json:"title,omitempty"`
	Resource string    `json:"resource,omitempty"`
	At       time.Time `json:"at"`
}

Event is one ledger line: an investigation opened, or an incident resolved.

type Ledger

type Ledger struct {
	// contains filtered or unexported fields
}

Ledger is an append-only outcome log with an in-memory open-index and a cached per-entry recall aggregate (the OpenCounts roll-up). The aggregate is built once by replaying the file at New and then maintained INCREMENTALLY under mu on every Open/Resolve, so OpenCounts is O(1) and never re-reads the file — it lived on the recall hot path, which previously replayed the whole JSONL per incident lookup.

func New

func New(path string) (*Ledger, error)

New opens (replaying) the ledger at path. An empty path returns a disabled, no-op ledger (the feature is off).

func (*Ledger) Episodes

func (l *Ledger) Episodes() ([]Episode, error)

Episodes replays the full ledger and turns every open into an Episode, pairing each resolve with the most-recent (LIFO) unresolved open for the same fingerprint — so recurrence is preserved (N opens + 1 resolve ⇒ N episodes, 1 resolved). Pairing is order-independent: a resolve that arrives BEFORE its open (a transient incident that cleared mid-investigation, so the resolve webhook landed before the open was recorded) is buffered and paired with the next open for that fingerprint. Episodes are returned in open order; all kinds are included. A disabled/empty ledger yields nil.

func (*Ledger) Open

func (l *Ledger) Open(e Event) error

Open records that an investigation completed for an incident (fingerprint).

func (*Ledger) OpenCounts

func (l *Ledger) OpenCounts() (map[string]Aggregate, error)

OpenCounts rolls recall episodes up per catalog entry (fresh investigations carry no entry). It is the input to recall decay: resolve-rate ≈ (Resolved+k)/(Recalls+k), and runs on the recall hot path once per incident lookup. It returns the cached aggregate — built once at New and maintained incrementally on every Open/Resolve — so it is O(entries) and never re-reads the file; the value equals a fresh full replay of the ledger for any event sequence below the maxPendingResolvesPerFingerprint cap — above the cap (pathological orphan-resolve load), dropped excess resolves mean the cache and an unbounded Episodes() replay may diverge. A disabled/empty ledger yields an empty (non-nil) map. The returned map is a fresh copy the caller may freely mutate.

Behaviour note: because the read moved to New/Reload, OpenCounts no longer performs file I/O and so can no longer return a read error — any error reading the ledger surfaces at construction (New) or re-sync (Reload) time, not per call. The error return is retained for signature stability and is always nil here.

func (*Ledger) Reload added in v0.3.0

func (l *Ledger) Reload() error

Reload re-replays the ledger file under the write lock and rebuilds the cached aggregate, open-index, and pairing state from scratch. The cache is otherwise maintained incrementally and so only reflects writes made through THIS process: in a multi-replica HA deployment sharing one ledger file, a replica that loses then re-acquires leadership would keep serving its pre-handover cache, missing every open/resolve another replica appended while it was a follower — stale aggregates and thus wrong recall-decay. Call Reload when this process (re-)acquires leadership so it re-syncs with those external writes. It takes the write lock (no torn read against a concurrent OpenCounts). A disabled/nil ledger is a no-op.

func (*Ledger) Resolve

func (l *Ledger) Resolve(fp string, at time.Time) (Episode, bool, error)

Resolve records that an incident's alert cleared. When it matches an open investigation it returns the Episode (with duration + kind) and ok=true.

func (*Ledger) Status

func (l *Ledger) Status() Status

Status reports whether the ledger is configured and whether its file is actually present/non-empty where this process runs. A disabled ledger (path=="") reports Configured=false.

type Status

type Status struct {
	Path       string // the configured ledger path ("" when disabled)
	Configured bool   // a non-empty ledger_path was set
	Present    bool   // the file exists (true even when empty)
	Events     int    // number of replayable events (0 for absent/empty)
}

Status is a cheap snapshot of the ledger's on-disk reality, used to tell apart "feature off" (Configured=false) from "configured but the file the curate pod can see is absent/empty" (Configured=true, Present=false or Events==0) — the silent-no-op the `lore curate` startup warning surfaces. It re-reads the file (no cached open-index) so it reflects what a fresh process actually sees.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL