app

package
v0.3.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: 67 Imported by: 0

Documentation

Overview

Package app holds the dependency-injection builders and config predicates that assemble the RunLore agent. They live here (instead of in cmd/lore behind main()) so the wiring that decides what ships is unit-testable.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func BuildApprovals

func BuildApprovals(cfg *config.Config, exec action.Executor, aud audit.Auditor, log *slog.Logger) *action.Approvals

BuildApprovals enables rung-2 approval-gated execution for action mode "approve" (requires a reachable cluster).

func BuildAuditor

func BuildAuditor(cfg *config.Config, log *slog.Logger) (audit.Auditor, func(), error)

BuildAuditor opens the append-only action audit log when configured, else a no-op. Validate already requires AuditLogPath for both executing modes (approve and auto), so the Nop fallback below only ever applies to off/suggest.

The existing hash chain is verified on open (OpenVerified). If it is broken (insertion, edit, or mid-chain deletion) the response is mode-gated, because this is where cfg is available:

  • approve/auto (RunLore executes + audits actions) → return an error so startup FAILS CLOSED: it must not append to, or act against, a chain whose integrity can no longer be vouched for.
  • off/suggest (nothing is executed) → log a WARNING and proceed: the auditor still opens and keeps appending, so a non-executing deployment isn't blocked.

An empty or absent chain is valid (not broken). Tail-truncation (dropping the most-recent records) leaves a valid prefix and is NOT detectable here — see docs/security-model.md.

func BuildAuto

func BuildAuto(cfg *config.Config, exec action.Executor, aud audit.Auditor, log *slog.Logger) *action.Auto

BuildAuto enables rung-3 unattended execution for action mode "auto" (requires a reachable cluster). Heavily gated: reversible-only, confidence-floored, rate- limited, kill-switchable, and audited. The rung is EXPERIMENTAL and FROZEN (FEAT-1): unattended execution contradicts the read-only-first posture and is the only path that turns a prompt-injected finding into a cluster mutation, so it gets no further investment and may be removed — prefer "approve", which captures nearly all the value. Recommend dry_run if you evaluate it.

func BuildCatalog

func BuildCatalog(ctx context.Context, cfg *config.Config, forgeTok ForgeToken, metrics *telemetry.Metrics, log *slog.Logger) *catalog.Catalog

BuildCatalog returns the kb_search backing store, or nil when no catalog is configured. With a Git URL it starts a background syncer (running on every replica, so a failover standby is already warm) that re-indexes after each pull; otherwise it loads a static mounted directory once.

func BuildCurator

func BuildCurator(cfg *config.Config, token ForgeToken, cat *catalog.Catalog, metrics *telemetry.Metrics, log *slog.Logger) *curator.Curator

BuildCurator returns a Curator when the GitHub App token + KB repo are configured, else nil.

func BuildFailureDebouncer

func BuildFailureDebouncer(cfg *config.Config, gp providers.GitOpsProvider, metrics *telemetry.Metrics, log *slog.Logger) *investigate.Debouncer

BuildFailureDebouncer wires a Debouncer whose "still failing?" re-check reads the workload's CURRENT Ready condition via GitOpsInspector.ResourceStatus. When the engine offers no inspector, the predicate is nil and the debouncer just waits out the window before enqueuing (still filtering instantly-clearing flaps). A zero window makes Debounce enqueue immediately.

func BuildGitOps

func BuildGitOps(cfg *config.Config, dc dynamic.Interface, log *slog.Logger) providers.GitOpsProvider

BuildGitOps builds the GitOps provider for the configured engine (flux default). The differ clones the GitOps source repo over HTTPS; it authenticates private repos with the shared GitHub App installation token (the App needs contents:read on the source repo). A nil token source (no App configured) means public/local repos only.

func BuildInvestigator

func BuildInvestigator(ctx context.Context, cfg *config.Config, gp providers.GitOpsProvider, approvals *action.Approvals, auto *action.Auto, metrics *telemetry.Metrics, ledger *outcome.Ledger, log *slog.Logger) (investigate.Investigator, *catalog.Catalog, error)

BuildInvestigator returns the LLM ReAct investigator when a model is configured, otherwise the read-only LogInvestigator. It also returns the catalog (nil when no model is configured or no catalog is wired).

func BuildJudgeModel

func BuildJudgeModel(cfg *config.Config, provider, baseURL, model, apiKeyEnv string) providers.ModelProvider

BuildJudgeModel builds the (stronger) grader model from --judge-* flags, falling back to the configured investigation model when unset.

func BuildModel

func BuildModel(cfg *config.Config, apiKey string) providers.ModelProvider

BuildModel builds the ModelProvider for the configured provider, applying the effective output-token cap (model.max_tokens, defaulted when unset) and the opt-in effort and thinking knobs.

func BuildModelAndTools

BuildModelAndTools assembles the model, investigation tools, and the instant-recall short-circuit from config + the GitOps provider. Shared by serve and investigate.

func BuildNotifier

func BuildNotifier(cfg *config.Config, log *slog.Logger) (*notify.Multi, error)

BuildNotifier assembles the configured chat notifiers (best-effort fan-out) via the notifier registry. Slack/Matrix (and any registered sink, e.g. the generic webhook) self-register; each Build reads its own config.

func BuildReinvestigator

func BuildReinvestigator(ctx context.Context, cfg *config.Config, gp providers.GitOpsProvider, metrics *telemetry.Metrics, log *slog.Logger) *investigate.Reinvestigator

BuildReinvestigator returns a poller that re-runs KB issues labelled "reinvestigate" and posts the fresh findings back, or nil when the forge isn't configured. RunLore polls the forge (outbound) — it has no inbound webhooks.

func BuildVerifyModel

func BuildVerifyModel(cfg *config.Config) providers.ModelProvider

BuildVerifyModel builds the optional cheaper model for the adversarial verify pass, inheriting any unset field from the main model. Returns nil when no model.verify override is configured (verify then runs on the main model).

func CatalogConfigured

func CatalogConfigured(cfg *config.Config) bool

CatalogConfigured reports whether the operator asked for a knowledge catalog (a mounted dir or a git-sync repo). It is independent of whether the load succeeded: ReadyFunc uses it to keep a configured-but-failed catalog (which BuildCatalog returns as nil) from collapsing readiness to pure leadership and serving incident traffic with no knowledge base.

func GitOpsFromKube

func GitOpsFromKube(cfg *config.Config, log *slog.Logger) providers.GitOpsProvider

GitOpsFromKube builds the GitOps provider from the ambient kubeconfig (best-effort).

func GitopsEngine

func GitopsEngine(cfg *config.Config) string

GitopsEngine returns the configured GitOps engine, defaulting to flux.

func KubeClientset

func KubeClientset(log *slog.Logger) *kubernetes.Clientset

KubeClientset builds a read-only clientset for pod-log access, or nil when no cluster is reachable (e.g. local runs without a kubeconfig).

func LogLedgerStartup

func LogLedgerStartup(log *slog.Logger, s outcome.Status)

LogLedgerStartup reports, at the right level, what the outcome ledger looks like to this `lore curate` process — turning the previously-silent no-op into a visible warning. The Queue + Recurrence passes read the ledger, but outcome.New succeeds even when the file is absent, so a misconfigured mount (the ledger lives on a volume the CronJob doesn't see — e.g. persistence not enabled, the path not under catalog.mountPath, or a fresh per-Job emptyDir) would silently produce zero work. We still run the passes; we just make the likely misconfiguration loud.

func ModelConfigured

func ModelConfigured(cfg *config.Config) bool

ModelConfigured reports whether a usable model is configured: a provider with a built-in default endpoint (anthropic, gemini), or any provider with a base_url.

func ModelProvider

func ModelProvider(cfg *config.Config) string

ModelProvider returns the configured model provider name (default "openai").

func NewHTTPServer

func NewHTTPServer(addr string, h http.Handler) *http.Server

NewHTTPServer builds the serving http.Server with every inbound bound set. Go's zero defaults leave each of these unlimited, exposing the long-lived server to Slowloris (slow header/body), unbounded idle keep-alives, and oversized headers. Payloads (Alertmanager/Slack) are small and synchronous, so 30s read/write is generous while still cutting off slow attackers; the body itself is capped per handler (1 MiB).

func NewModelClient

func NewModelClient(provider, baseURL, model, apiKey string, maxTokens int, effort, thinking string) providers.ModelProvider

NewModelClient builds a ModelProvider for a wire protocol + endpoint. maxTokens is the per-request output-token ceiling passed through to the provider. effort is the opt-in reasoning knob (config-validated per provider; "" = omitted); gemini does not take it — config.Validate rejects effort for that provider. thinking is the opt-in adaptive-thinking knob (anthropic-only, config-validated; "" = omitted); only the anthropic client consumes it.

func OutcomeKind

func OutcomeKind(recalled bool) string

OutcomeKind labels an outcome-ledger open as a recall (cache hit) or a fresh finding.

func PodName

func PodName() string

PodName returns this pod's identity for leader election.

func PodNamespace

func PodNamespace() string

PodNamespace resolves the namespace from the downward API or the service-account mount.

func ReadyFunc

func ReadyFunc(leader func() bool, cat *catalog.Catalog, configured bool) func() bool

ReadyFunc gates readiness on leadership AND a warm catalog. When a catalog is configured, the leader must NOT advertise ready until its knowledge base is loaded and warm — otherwise it would serve incident traffic blind. This distinguishes the two ways BuildCatalog returns a nil catalog:

  • configured but the load failed (configured=true, cat=nil): stay 503. A static catalog has no syncer to recover, so the pod stays not-ready and the misconfiguration surfaces loudly instead of silently serving with no KB.
  • not configured at all (configured=false, cat=nil): no catalog gate; readiness is pure leadership.

A configured-but-not-yet-warm catalog (git-sync NewEmpty, cat!=nil && !Ready()) is also held at 503 until its first successful sync.

func RequirePagerDutyAuth added in v0.3.0

func RequirePagerDutyAuth(cfg *config.Config, enabled bool, secret string) error

RequirePagerDutyAuth is the PagerDuty analogue of RequireWebhookAuth. The PagerDuty source authenticates /webhook/pagerduty with its own X-PagerDuty-Signature verification (not the shared server.webhook_token_env bearer token), so the shared guard does not cover it. When the source is enabled and a model is configured, it must carry a signing secret — otherwise an unauthenticated caller could drive (and bill) the model. Fail closed on the serve path only, mirroring RequireWebhookAuth.

func RequireWebhookAuth

func RequireWebhookAuth(cfg *config.Config, webhookToken string) error

RequireWebhookAuth fails closed on the serve path when the LLM investigator is wired but the alert webhook is anonymous. The webhook's labels/annotations flow verbatim into the LLM prompt (and bill the model), so an unauthenticated caller must not reach it once a model is configured — regardless of actions.mode. This lives on the serve path, NOT in config.Validate: Validate is shared by every subcommand (e.g. `lore investigate` legitimately needs a model and has no webhook), so the requirement is scoped to where the webhook is actually served. It mirrors the approval-token fail-closed guard.

func RestConfig

func RestConfig() (*rest.Config, error)

RestConfig builds a Kubernetes REST config from in-cluster config, falling back to the local kubeconfig (respecting $KUBECONFIG, then ~/.kube/config).

func RunCatalog

func RunCatalog(args []string) error

RunCatalog dispatches catalog subcommands.

func RunCatalogSync

func RunCatalogSync(args []string) error

RunCatalogSync clones/pulls the catalog Git repo into the mirror and reports the indexed entry count — a one-shot of the background sync that serve runs.

func RunCurate

func RunCurate(args []string) error

RunCurate grooms the KB backlog (Phase-2 curation agent). It runs the backlog-dedup pass (collapses duplicate open PRs across history) and the lifecycle sweep (closes stale, unprotected PRs by forge age). When outcome.ledger_path is configured, it also runs the Queue pass (promotes solved→ready-to-merge when the incident resolves) and the Recurrence pass (opens a knowledge-gap issue for repeatedly-unresolved patterns).

func RunEval

func RunEval(args []string) error

RunEval replays recorded incident cases through the investigation loop and reports the RCA-identification rate. Requires a configured model.

func RunEvalCompare added in v0.3.0

func RunEvalCompare(cfg *config.Config, comparePath, casesDir, reportDir, stamp string, n int,
	jProvider, jBaseURL, jModel, jKeyEnv string) error

RunEvalCompare benchmarks every model in the comparison spec against the replay suite and writes one aggregated report (markdown + JSON) to reportDir. The judge is fixed across entries (blind grading already anonymizes which model produced a result), so scores are comparable. It reuses the single-run replay machinery per entry via eval.ComparisonRunner.

func RunEvalLive

func RunEvalLive(cfg *config.Config, scnDir, recordDir, reportDir, prevReport, stamp string, n int,
	jProvider, jBaseURL, jModel, jKeyEnv string) error

RunEvalLive runs the live-fire campaign and writes a dated report.

func RunInvestigate

func RunInvestigate(args []string) error

RunInvestigate runs a single on-demand investigation and prints the findings.

func RunLeaderElection

func RunLeaderElection(ctx context.Context, cfg *config.Config, cs *kubernetes.Clientset, leader *atomic.Bool, log *slog.Logger, startWork func(context.Context))

RunLeaderElection blocks running Lease-based leader election; the leader runs startWork and reports ready. Lost leadership cancels the work context.

func RunMCP

func RunMCP(version string, args []string) error

RunMCP serves RunLore's GitOps what-changed capability over the Model Context Protocol (stdio JSON-RPC), so an MCP client (e.g. HolmesGPT) can call it as a toolset. stdout is the protocol channel; logs go to stderr. Read-only. version is injected at build time and stays in package main; it is passed through to the MCP server's advertised server info.

func RunServe

func RunServe(version string, args []string) error

RunServe runs the in-cluster agent: it loads config, builds the investigation pipeline (kube/GitOps/model/catalog/actions), runs leader election, and serves the webhook + readiness endpoints until SIGTERM, then drains gracefully. version is injected at build time and stays in package main; it is passed through for the build-info gauge.

func SetMemoryLimitFromCgroup

func SetMemoryLimitFromCgroup(log *slog.Logger)

SetMemoryLimitFromCgroup sets GOMEMLIMIT to ~90% of the cgroup v2 memory limit so the Go GC respects the container's memory cap — keeping the heap under a soft ceiling and returning memory to the OS under pressure — instead of letting RSS grow across investigations until the cgroup OOM-kills the process. No-op when GOMEMLIMIT is set explicitly, or there is no cgroup memory limit.

Types

type ForgeToken

type ForgeToken func(context.Context) (string, error)

ForgeToken mints GitHub App installation tokens.

func BuildForgeTokenSource

func BuildForgeTokenSource(cfg *config.Config, log *slog.Logger) ForgeToken

BuildForgeTokenSource builds the GitHub App installation-token source shared by the curator (issues/PRs) and catalog git-sync (clone auth) — one identity for both forge writes and reads. Returns nil when no App is configured.

Jump to

Keyboard shortcuts

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