incident

package
v0.13.21 Latest Latest
Warning

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

Go to latest
Published: Jun 18, 2026 License: Apache-2.0 Imports: 16 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

View Source
var DefaultBucketConfigs = map[Severity]BucketConfig{
	SeverityError:   {Size: 20, RefillRate: 10},
	SeverityWarning: {Size: 10, RefillRate: 5},
	SeverityInfo:    {Size: 5, RefillRate: 2},
}

DefaultBucketConfigs are the production token-bucket parameters. Critical severity is not listed — it is always allowed (infinite bucket).

View Source
var DefaultPingDelays = PingDelays{
	Initial:    500 * time.Millisecond,
	Max:        10 * time.Second,
	ResetAfter: 30 * time.Second,
}

DefaultPingDelays are the production backoff parameters.

View Source
var ErrBlobEvicted = errors.New("blob evicted from store")

ErrBlobEvicted is returned by Read when the requested blob was evicted.

Functions

func Canonicalize

func Canonicalize(msg string) string

Canonicalize strips volatile parts of msg (timestamps, addresses, UUIDs, line numbers on non-app frames) so that two occurrences of the same logical error produce the same fingerprint regardless of when or where they fired.

func CompactDigestText added in v0.13.16

func CompactDigestText(p PingPayload) string

CompactDigestText renders the one-line digest summary for cross-process transport (daemon → STREAM-EVENTS → consumer). Exported wrapper over the internal compact formatter.

func NextPingDelay

func NextPingDelay(occurrence int, delays PingDelays) time.Duration

NextPingDelay computes the ping delay for the nth occurrence of a fingerprint. occurrence is 1-based. The sequence doubles from Initial each occurrence until Max.

func PingPayloadSize

func PingPayloadSize(p PingPayload) int

PingPayloadSize returns the JSON-marshalled byte count of a payload for size assertions in tests.

Types

type ActivityDetector

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

ActivityDetector tracks agent hook activity to defer non-critical pings while the agent is busy making tool calls. When the agent goes idle (no hook events for idleWindow), onIdle is called so pending pings can flush.

Critical-severity events bypass the detector entirely — callers should not gate critical pings on IsActive().

func NewActivityDetector

func NewActivityDetector(activeWindow, idleWindow time.Duration, onIdle func()) *ActivityDetector

NewActivityDetector creates a detector. activeWindow is how long after the last hook event the agent is considered active (default 500ms). idleWindow is how long without activity before onIdle fires (default 3s). onIdle must not block.

func (*ActivityDetector) IsActive

func (ad *ActivityDetector) IsActive() bool

IsActive returns true if a hook event was seen within the last activeWindow.

func (*ActivityDetector) RecordHook

func (ad *ActivityDetector) RecordHook()

RecordHook records a hook event (pre-tool-use, post-tool-use, etc.). Resets the idle timer so onIdle fires idleWindow from now.

func (*ActivityDetector) Stop

func (ad *ActivityDetector) Stop()

Stop cancels the idle timer without firing it. Call on shutdown.

type BlobRef

type BlobRef struct {
	Hash string // sha256 hex of raw payload
	Size int
	MIME string // "text/plain", "application/json", "text/html"
}

BlobRef points to a payload stored in a BlobStore.

type BlobStore

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

BlobStore is a bytes-bounded content-addressed in-memory store with LRU eviction. Write computes the sha256 hash, deduplicates identical payloads, and evicts the least-recently-used blob when the budget is exceeded.

Writes are dispatched via a bounded channel (256 entries) to a background goroutine so callers never block on lock contention. Close drains the queue and stops the goroutine.

WriteAsync returns a BlobRef immediately (hash computed synchronously) and defers the actual storage. If the async queue is full the content may never land — readers fall back to IncidentEvent.Summary in that case.

func NewBlobStore

func NewBlobStore(maxBytes int64) *BlobStore

NewBlobStore creates a BlobStore limited to maxBytes of payload storage. Pass 0 for the default (16MB).

func (*BlobStore) Close

func (bs *BlobStore) Close()

Close stops the background goroutine after draining pending writes.

func (*BlobStore) Read

func (bs *BlobStore) Read(hash string) ([]byte, string, error)

Read retrieves the payload for hash. Returns ErrBlobEvicted if the blob was evicted since writing.

func (*BlobStore) Stats

func (bs *BlobStore) Stats() (used, max int64, count int)

Stats returns current usage for observability.

func (*BlobStore) Write

func (bs *BlobStore) Write(content []byte, mime string) (BlobRef, error)

Write stores content and returns a BlobRef. Identical content (same sha256) is stored only once. If the store is over budget the oldest entry is evicted. Write never blocks the caller: it is enqueued to a background goroutine. The returned channel receives the result when the write completes.

func (*BlobStore) WriteAsync

func (bs *BlobStore) WriteAsync(content []byte, mime string) BlobRef

WriteAsync computes the sha256 hash synchronously and returns a BlobRef immediately, then enqueues the actual content write to a background goroutine. If the async queue is full the ref is still returned but the content may never land — readers should fall back to IncidentEvent.Summary in that case.

type BucketConfig

type BucketConfig struct {
	Size       float64 // maximum tokens (burst capacity)
	RefillRate float64 // tokens added per second
}

BucketConfig configures a token bucket for one severity level.

type Bus

type Bus interface {
	Publish(IncidentEvent)
}

Bus is the central sink for all incident events. MPSCBus is the production implementation; NopBus discards events.

type ChannelNotifyFn

type ChannelNotifyFn func(content string, payload PingPayload) error

ChannelNotifyFn sends a claude/channel notification. content is compact text.

type Coalescer

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

Coalescer schedules ping emissions with exponential backoff per fingerprint. The first occurrence fires after Initial; repeats are delayed more. Severity escalation (e.g. warning → error on the same fingerprint) immediately cancels the pending timer and fires the ping.

func NewCoalescer

func NewCoalescer(delays PingDelays, pingFn func(IncidentEvent)) *Coalescer

NewCoalescer creates a Coalescer that calls pingFn when a ping should emit. pingFn must not block.

func (*Coalescer) ForceFlush

func (c *Coalescer) ForceFlush(fp string)

ForceFlush immediately emits a pending ping for fp, if any, and resets the slot.

func (*Coalescer) Schedule

func (c *Coalescer) Schedule(ev IncidentEvent)

Schedule registers an event for coalesced ping emission. If a pending ping exists for the same fingerprint, its timer is rescheduled (potentially with a longer delay) unless the new event's severity is higher — in that case the pending timer is cancelled and the ping fires immediately.

func (*Coalescer) Stop

func (c *Coalescer) Stop()

Stop cancels all pending timers without firing them. Call on shutdown.

type Context

type Context struct {
	ProcessID   string
	ProxyID     string
	SessionID   string
	ProjectPath string
	URL         string
	PID         int
	PGID        int
	Port        int
}

Context carries identifiers for the resources involved in the incident.

type DedupEntry

type DedupEntry struct {
	First IncidentEvent
	Last  IncidentEvent
	Count int
	// contains filtered or unexported fields
}

DedupEntry tracks merged occurrences of a recurring incident fingerprint within a sliding deduplication window.

type Deduplicator

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

Deduplicator merges repeated IncidentEvents that share a fingerprint into one DedupEntry, incrementing Count on each duplicate. The window is sliding: each duplicate resets the expiry. A fingerprint seen after its window expires is treated as a new occurrence.

func NewDeduplicator

func NewDeduplicator(window time.Duration) *Deduplicator

NewDeduplicator creates a Deduplicator with the given sliding window. Use 30s in production; tests can use shorter values.

func (*Deduplicator) Ingest

func (d *Deduplicator) Ingest(sessionID string, ev IncidentEvent) (merged bool, entry *DedupEntry)

Ingest adds ev to the deduplicator. Returns (true, existing entry) when ev deduplicates against a live entry; returns (false, new entry) on first occurrence or after the window has expired.

func (*Deduplicator) Len

func (d *Deduplicator) Len() int

Len returns the number of currently tracked fingerprints.

func (*Deduplicator) Trim

func (d *Deduplicator) Trim()

Trim removes expired entries, reclaiming memory.

type FlowController

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

FlowController is a per-severity token bucket that rate-limits ping emissions. Critical severity is never throttled. Over-budget events should still be inboxed — only their ping emission is suppressed.

func NewFlowController

func NewFlowController(configs map[Severity]BucketConfig) *FlowController

NewFlowController creates a FlowController using the provided per-severity configs. Any severity not in configs (including SeverityCritical) is unthrottled.

func (*FlowController) TryPing

func (fc *FlowController) TryPing(sev Severity) bool

TryPing returns true if a ping for the given severity is allowed. Critical always returns true. Other severities consume a token; if the bucket is empty TryPing returns false (caller should inbox but not ping).

type Inbox

type Inbox struct {
	SessionID string
	// contains filtered or unexported fields
}

Inbox is a bounded per-session incident inbox with 4 severity bands (critical, error, warning, info). Concurrent-safe.

func NewInbox

func NewInbox(sessionID string) *Inbox

NewInbox creates a session inbox with default 100-entry band capacities.

func (*Inbox) Cursor

func (inbox *Inbox) Cursor() time.Time

Cursor returns the last acknowledged position (time of last MarkRead).

func (*Inbox) GC

func (inbox *Inbox) GC()

GC removes Read entries older than staleReadAge from all bands.

func (*Inbox) Ingest

func (inbox *Inbox) Ingest(entry *InboxEntry) InboxDelta

Ingest adds or merges an InboxEntry into the appropriate severity band. If the fingerprint already exists in any band, Count is incremented and LastSeenAt updated; severity escalation moves the entry to a higher band. Returns the resulting InboxDelta.

func (*Inbox) LoadCritical

func (inbox *Inbox) LoadCritical(path string) error

LoadCritical loads previously saved critical entries. Silently ignores missing file (first session).

func (*Inbox) MarkRead

func (inbox *Inbox) MarkRead(fingerprints []string, advanceCursor bool)

MarkRead marks entries as read and optionally advances the cursor.

func (*Inbox) Query

func (inbox *Inbox) Query(filter QueryFilter) ([]InboxEntry, Stats)

Query returns entries matching filter. Results are sorted newest first.

func (*Inbox) SaveCritical

func (inbox *Inbox) SaveCritical(path string) error

SaveCritical persists unread critical entries to path as JSON.

func (*Inbox) Stats

func (inbox *Inbox) Stats() Stats

Stats returns a snapshot of band counts and inbox health.

func (*Inbox) Subscribe

func (inbox *Inbox) Subscribe() (<-chan InboxDelta, func())

Subscribe returns a buffered channel delivering InboxDelta on each Ingest. Slow consumers receive dropped silently. Call cancel when done.

type InboxDelta

type InboxDelta struct {
	Entry     *InboxEntry
	IsNew     bool // false = fingerprint-merge
	Escalated bool // severity moved entry to a higher band
}

InboxDelta is delivered to Subscribe subscribers on each Ingest.

type InboxEntry

type InboxEntry struct {
	Fingerprint  string         `json:"fingerprint"`
	FirstSeenAt  time.Time      `json:"first_seen_at"`
	LastSeenAt   time.Time      `json:"last_seen_at"`
	Count        int            `json:"count"`
	Sample       *IncidentEvent `json:"sample,omitempty"`
	SampleURLs   []string       `json:"sample_urls,omitempty"`   // up to maxSampleURLs distinct
	DistinctURLs int            `json:"distinct_urls,omitempty"` // distinct URLs seen, capped
	Severity     Severity       `json:"severity"`
	Read         bool           `json:"read"`
	// contains filtered or unexported fields
}

InboxEntry is a deduplicated, severity-banded record of an incident fingerprint.

type IncidentEvent

type IncidentEvent struct {
	ID          string
	Fingerprint string // sha256(source|category|canonical_msg|location)[:16]
	Type        MessageType
	ReceivedAt  time.Time
	Source      Source
	Severity    Severity
	Category    string // finer-grained, e.g. "TypeError", "ECONNREFUSED"
	Summary     string // ≤200 bytes, single line
	PayloadRef  *BlobRef
	Ctx         Context
	Remediation Remediation
}

IncidentEvent is the canonical envelope for all 11 signal sources. ID is a time-ordered UUID v7 string — monotonic, sortable, cursor-friendly. Summary is capped at 200 bytes; oversized payloads live in BlobStore via PayloadRef.

func FromAlertMatch

func FromAlertMatch(m *overlay.AlertMatch, processID string) IncidentEvent

FromAlertMatch converts a pattern-matched line from the AlertScanner into an IncidentEvent. Build-failure patterns (webpack, vite, nextjs, rebuild, or any pattern whose description contains "build"/"compile") use SourceBuildFail; all others use SourceProcessAlert.

func FromFrontendError

func FromFrontendError(fe proxy.FrontendError, proxyID string) IncidentEvent

FromFrontendError converts a browser JS error captured by the proxy into an IncidentEvent. The error type name is extracted as the category.

func FromHTTPEntry

func FromHTTPEntry(he proxy.HTTPLogEntry, proxyID string) (IncidentEvent, bool)

FromHTTPEntry converts a logged HTTP request/response into an IncidentEvent. Returns (event, true) for 4xx/5xx responses; returns (zero, false) for 2xx/3xx which do not warrant incidents.

func FromHookStopFailure

func FromHookStopFailure(sessionID, errMsg, errDetails string) IncidentEvent

FromHookStopFailure creates an IncidentEvent for a Claude Code StopFailure hook event (the agent turn ended due to an API error). Uses plain primitives to avoid importing the daemon package.

sessionID is the Claude Code session that failed. errMsg is the short error label. errDetails carries the full error body (may be empty).

func FromPortConflict

func FromPortConflict(port, pid int, processName string) IncidentEvent

FromPortConflict converts a port-conflict detection record into an IncidentEvent. Uses plain primitives to avoid importing the daemon package.

port is the blocked port number. pid is the blocking process's OS PID. processName is a human-readable label for the blocking process (may be empty).

func FromProcessExit

func FromProcessExit(processID, reason string, exitCode int, stderrTail string) IncidentEvent

FromProcessExit converts a process death record into an IncidentEvent. Uses plain primitive params so the incident package stays free of daemon imports (avoiding a cycle when daemon wires the bus in L8).

reason is "stopped" | "crash" | "signal". exitCode is the OS exit code. stderrTail is the last N lines of stderr captured on exit (may be empty).

func FromProxyDiagnostic

func FromProxyDiagnostic(d proxy.ProxyDiagnostic, proxyID string) (IncidentEvent, bool)

FromProxyDiagnostic converts a proxy diagnostic event into an IncidentEvent. Info-level diagnostics are filtered out (returns zero, false). Transport-layer events (connection refused/timeout) use SourceTransportErr; all others use SourceProxyDiag.

func FromShutdown

func FromShutdown(reason string) IncidentEvent

FromShutdown creates an IncidentEvent signalling daemon/session shutdown.

func NewIncidentEvent

func NewIncidentEvent(src Source, sev Severity, category, msg string, ctx Context, store *BlobStore) IncidentEvent

NewIncidentEvent builds an IncidentEvent from the raw message and optional full payload. If payload is non-nil and longer than 1KB it is stored in store (if non-nil) and referenced via PayloadRef; otherwise it is truncated into Summary.

type IncidentView

type IncidentView struct {
	ID          string      `json:"id"`
	Fingerprint string      `json:"fingerprint"`
	FirstSeen   time.Time   `json:"first_seen"`
	LastSeen    time.Time   `json:"last_seen"`
	Count       int         `json:"count"`
	Severity    Severity    `json:"severity"`
	Source      Source      `json:"source"`
	Category    string      `json:"category,omitempty"`
	Summary     string      `json:"summary,omitempty"`
	Payload     *string     `json:"payload,omitempty"` // hydrated when detail="full"
	Ctx         Context     `json:"context,omitempty"`
	Remediation Remediation `json:"remediation,omitempty"`
	Read        bool        `json:"read"`
}

IncidentView is the pull-side projection of an InboxEntry, enriched with remediation guidance. Used by get_incidents output and AggregateRemediation.

type MCPNotifyFn

type MCPNotifyFn func(level string, payload PingPayload) error

MCPNotifyFn sends an MCP Log notification. level is "error", "warning", or "info".

type MPSCBus

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

MPSCBus is a backpressure-safe, non-blocking event bus. Multiple producers call Fire or Publish; a single dispatch goroutine fans events to all active session pipelines. If the inbound channel is full, the newest event is dropped and the drop counter incremented. Every 100th drop synthesises one meta:bus_overflow warning incident so the AI agent sees bus pressure.

Session lifecycles:

  • AddSession: creates sessionPipeline and starts its dispatch goroutine.
  • RemoveSession: signals the goroutine to stop and waits up to 2s.
  • Close: removes all sessions and stops the bus.

func NewMPSCBus

func NewMPSCBus(pingConfig *PingConfig) *MPSCBus

NewMPSCBus creates a ready-to-use MPSCBus. pingConfig may be nil for a headless bus (no pinger). The bus starts the central dispatch goroutine immediately.

func (*MPSCBus) AddSession

func (b *MPSCBus) AddSession(sessionID string, mcpNotify MCPNotifyFn, channelNotify ChannelNotifyFn, ptyInject PTYInjectFn)

AddSession registers a new session pipeline. It is idempotent — calling again with the same sessionID replaces the old pipeline (the old one is torn down first).

mcpNotify, channelNotify, and ptyInject may all be nil (pinger channels are skipped). pingConfig may be nil; DefaultPingConfig() is used in that case.

func (*MPSCBus) Close

func (b *MPSCBus) Close()

Close shuts down the bus and all session pipelines.

func (*MPSCBus) Dropped

func (b *MPSCBus) Dropped() int64

Dropped returns the cumulative count of dropped events.

func (*MPSCBus) Fire

func (b *MPSCBus) Fire(ev *IncidentEvent)

Fire is the single non-blocking entry point for signal producers. It must complete in <1μs even when the bus is saturated.

func (*MPSCBus) HasSession added in v0.13.3

func (b *MPSCBus) HasSession(sessionID string) bool

HasSession reports whether a pipeline exists for the given session.

func (*MPSCBus) MarkReadSession added in v0.13.3

func (b *MPSCBus) MarkReadSession(sessionID string, fingerprints []string, advanceCursor bool)

MarkReadSession marks entries as read in the given session's inbox. No-op if the session has no pipeline.

func (*MPSCBus) Publish

func (b *MPSCBus) Publish(ev IncidentEvent)

Publish implements the Bus interface. It wraps Fire for callers that own the event value (not a pointer).

func (*MPSCBus) QuerySession added in v0.13.3

func (b *MPSCBus) QuerySession(sessionID string, filter QueryFilter) ([]InboxEntry, Stats)

QuerySession queries the incident inbox for the given session. Returns nil entries and empty stats if no pipeline exists for the session.

func (*MPSCBus) RemoveSession

func (b *MPSCBus) RemoveSession(sessionID string)

RemoveSession tears down the session pipeline for sessionID and waits up to sessionDrainWindow for in-flight events to finish.

type MessageType added in v0.13.16

type MessageType string

MessageType partitions the agent-inbound queue into per-type lanes. The error lane is severity-banded; drawing/comment lanes are FIFO. New types are added here plus a lane config — the gate/digest machinery is type-agnostic.

const (
	// MessageError is the lane for diagnostics, HTTP errors, crashes, etc.
	MessageError MessageType = "error"
	// MessageDrawing is the lane for sketch-mode wireframes.
	MessageDrawing MessageType = "drawing"
	// MessageComment is the lane for floating-panel user messages.
	MessageComment MessageType = "comment"
)

type NopBus

type NopBus struct{}

NopBus discards every event published to it.

func (NopBus) Publish

func (NopBus) Publish(IncidentEvent)

type PTYInjectFn

type PTYInjectFn func(line string) error

PTYInjectFn injects a single-line status string into PTY stdin.

type PingConfig

type PingConfig struct {
	MCPNotifications bool
	ChannelEnabled   bool
	PTYInjection     bool
	MaxTopFPs        int        // 0 → defaultMaxTopFingerprints
	IncludeSummary   bool       // include short summary text per top entry
	Delays           PingDelays // coalesce backoff; zero → DefaultPingDelays
}

PingConfig controls which channels emit pings and their content density.

type PingDelays

type PingDelays struct {
	Initial    time.Duration // first ping delay (default 500ms)
	Max        time.Duration // backoff ceiling (default 10s)
	ResetAfter time.Duration // inactivity window before counter resets (default 30s)
}

PingDelays configures the exponential backoff for coalesced ping emission. Initial is the first-occurrence delay; each repeat doubles it up to Max. After ResetAfter of no activity on a fingerprint the counter resets to Initial.

type PingEmitter

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

PingEmitter subscribes to an Inbox, applies flow + activity gating, and fans pings out to up to 3 channels (MCP log, claude/channel, PTY stdin).

Critical events bypass the activity deferral and coalesce timer. Non-critical events are debounced via a session-level Coalescer slot.

func NewPingEmitter

func NewPingEmitter(
	inbox *Inbox,
	config PingConfig,
	flow *FlowController,
	activity *ActivityDetector,
	mcpNotify MCPNotifyFn,
	channelNotify ChannelNotifyFn,
	ptyInject PTYInjectFn,
) *PingEmitter

NewPingEmitter creates and starts a PingEmitter. Stop() must be called to release the subscription goroutine. Any of mcpNotify, channelNotify, or ptyInject may be nil (that channel is skipped).

activity.onIdle is not set by NewPingEmitter — the caller should wire it with: `ForceFlush(sessionPingFP)` on the returned emitter's coalescer if activity-triggered flushing is desired (typically in L8 wiring).

func (*PingEmitter) ForceFlushCoalesce

func (pe *PingEmitter) ForceFlushCoalesce()

ForceFlushCoalesce immediately emits any pending coalesced ping. Intended for activity-idle callbacks wired by L8.

func (*PingEmitter) Stop

func (pe *PingEmitter) Stop()

Stop unsubscribes from the inbox and cancels all pending pings.

type PingPayload

type PingPayload struct {
	Type    string     `json:"type"`    // "agnt.incident_ping"
	Version int        `json:"version"` // 1
	Session string     `json:"session"`
	Summary PingStats  `json:"summary"`
	Top     []TopEntry `json:"top,omitempty"`
}

PingPayload is the stable schema for all channels. Target size < 2KB.

type PingStats

type PingStats struct {
	Critical int `json:"critical"`
	Error    int `json:"error"`
	Warning  int `json:"warning"`
	Info     int `json:"info"`
	New      int `json:"new"` // unread entries at time of ping
}

PingStats is the compact summary inside PingPayload.

type QueryFilter

type QueryFilter struct {
	Severities []Severity // empty = all
	Since      time.Time  // zero = no lower bound
	UnreadOnly bool
	Limit      int // 0 = all
}

QueryFilter narrows which entries Query returns.

type Remediation

type Remediation struct {
	PrimaryTool  string         `json:"primary_tool,omitempty"`
	PrimaryArgs  map[string]any `json:"primary_args,omitempty"`
	FallbackTool string         `json:"fallback_tool,omitempty"`
	FallbackArgs map[string]any `json:"fallback_args,omitempty"`
	SkillHint    string         `json:"skill_hint,omitempty"`
}

Remediation hints which MCP tool and skill should address this incident. Populated by the L7 routing table; zero value means "no hint".

func Resolve

func Resolve(ev *IncidentEvent) Remediation

Resolve returns remediation guidance for ev. Never returns a zero Remediation.

type Severity

type Severity string

Severity orders incidents by urgency.

const (
	SeverityCritical Severity = "critical"
	SeverityError    Severity = "error"
	SeverityWarning  Severity = "warning"
	SeverityInfo     Severity = "info"
)

type Source

type Source string

Source identifies where the incident originated.

const (
	SourceBrowserJS     Source = "browser_js"
	SourceHTTP5xx       Source = "http_5xx"
	SourceHTTP4xx       Source = "http_4xx"
	SourceTransportErr  Source = "transport_err"
	SourceProxyDiag     Source = "proxy_diag"
	SourceProcessAlert  Source = "process_alert"
	SourceProcessOutput Source = "process_output"
	SourceProcessCrash  Source = "process_crash"
	SourceBuildFail     Source = "build_fail"
	SourcePortConflict  Source = "port_conflict"
	SourceShutdown      Source = "shutdown"
	SourceHookStopFail  Source = "hook_stop_failure"
	SourceChaosSwallow  Source = "chaos_swallowed_error"
)

type Stats

type Stats struct {
	Critical     int       `json:"critical"`
	Error        int       `json:"error"`
	Warning      int       `json:"warning"`
	Info         int       `json:"info"`
	Dropped      int64     `json:"dropped"`
	OldestUnread time.Time `json:"oldest_unread,omitempty"`
	Cursor       time.Time `json:"cursor,omitempty"`
}

Stats is a point-in-time snapshot of inbox health.

type SwallowDetector added in v0.13.20

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

SwallowDetector implements the chaos "swallowed error" heuristic: when the chaos engine injects an error-status fault into a proxied response and the application produces NO app-side error signal (a frontend JS error on the same proxy) within a window, the fault was silently eaten — a real defect in the app's error handling that the chaos run exists to surface.

The detector is a pure correlation state machine driven by three calls:

  • RecordFault — an injected fault was logged (pending until proven handled)
  • RecordAppError — an app-side error arrived; clears recent pending faults on that proxy (the app surfaced something, so it did not swallow)
  • Sweep — pending faults older than the window with no matching app error become swallowed-error incidents

Time is passed in explicitly (no internal clock) so the daemon can drive it with a ticker and tests can drive it deterministically.

Correlation is per-proxy and time-windowed, not per-URL: a frontend JS error rarely names the request that triggered it, so any app error within the window after an injected fault counts as "handled". This is a heuristic by construction — it trades precision for catching the silent-failure case that nothing else can observe.

func NewSwallowDetector added in v0.13.20

func NewSwallowDetector(window time.Duration) *SwallowDetector

NewSwallowDetector returns a detector with the given correlation window. A non-positive window falls back to two seconds.

func (*SwallowDetector) RecordAppError added in v0.13.20

func (d *SwallowDetector) RecordAppError(proxyID string, now time.Time)

RecordAppError records that the app produced an error on proxyID. It clears every pending fault on that proxy whose injection time is within the window ending at now — the app surfaced an error in response, so those faults were handled, not swallowed.

func (*SwallowDetector) RecordFault added in v0.13.20

func (d *SwallowDetector) RecordFault(proxyID, url string, status int, now time.Time)

RecordFault registers an injected error-status fault as pending. It becomes a swallowed-error incident on a later Sweep unless RecordAppError clears it first.

func (*SwallowDetector) Sweep added in v0.13.20

func (d *SwallowDetector) Sweep(now time.Time) []IncidentEvent

Sweep returns swallowed-error incidents for every pending fault older than the window (no app error cleared it in time) and drops them from the pending set. Faults still inside the window are retained for a future sweep.

type ToolSuggestion

type ToolSuggestion struct {
	Tool      string         `json:"tool"`
	Args      map[string]any `json:"args,omitempty"`
	Rationale string         `json:"rationale"`
}

ToolSuggestion is a pre-filled tool call with a one-line rationale.

func AggregateRemediation

func AggregateRemediation(incidents []IncidentView) (skill string, tools []ToolSuggestion)

AggregateRemediation picks the dominant skill+tools across a slice of incidents, weighted by occurrence count.

type TopEntry

type TopEntry struct {
	Fingerprint string   `json:"fp"`
	Category    string   `json:"category,omitempty"`
	Count       int      `json:"count"`
	Severity    Severity `json:"severity"`
	Summary     string   `json:"summary,omitempty"`
}

TopEntry is one fingerprint row in PingPayload.Top.

Jump to

Keyboard shortcuts

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