composite_error

package
v0.19.930 Latest Latest
Warning

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

Go to latest
Published: May 13, 2026 License: AGPL-3.0 Imports: 4 Imported by: 0

Documentation

Overview

Package composite_error defines the typed, catalogable error abstraction used across the Nuon platform.

See specs/composite-errors.md for design rationale.

At a high level:

  • A CompositeError is a typed Go value (one struct per error type) that knows how to render itself for users.
  • Each type registers a factory in the in-memory catalog via init(), mirroring how queue signals are registered.
  • Parsers (also registered in the catalog) consume raw error material (Temporal errors, runner stderr, terraform output, …) and emit typed CompositeErrors via a dispatch pipeline.
  • The conductor / step finalizer honors optional capability interfaces on a CompositeError (see directive.go) to override step directives (fail-fast, retry, downgrade, …).

Phase 1 lands the abstraction and the catalog only; the helpers, GORM models, and parser implementations live alongside ctl-api.

Index

Constants

View Source
const SourceSnippetMax = 8 * 1024

SourceSnippetMax caps how much raw input we persist on a CompositeError row.

Variables

This section is empty.

Functions

func CapSnippet

func CapSnippet(s string) string

CapSnippet truncates s to at most SourceSnippetMax bytes, appending an ellipsis marker when truncation occurs.

Types

type CompositeError

type CompositeError interface {
	Type() Type
	Domain() Domain
	Severity() Severity

	// Render produces the user-facing view from the typed fields on the
	// implementing struct. Always called at read time. The TitleCached /
	// SummaryCached columns on the persisted row are denormalized copies
	// populated from Render() at write time and used only for admin
	// search / listing.
	Render(ctx context.Context) Render
}

CompositeError is the typed, in-memory view of a stored composite error.

Implementations live alongside ctl-api under services/ctl-api/internal/app/composite_errors/types/<name>/ and register themselves into the catalog via init().

A CompositeError MUST be JSON round-trippable: marshalling the implementing struct and unmarshalling into a fresh value produced by the catalog factory must yield an equivalent value. The persisted row's Data column holds exactly the JSON representation of the implementing struct.

type Directive

type Directive struct {
	Kind DirectiveKind `json:"kind"`
}

Directive is the override an error type can apply to a step's outcome. The shape is intentional: future fields (MaxRetries, Backoff, …) can be added without breaking the capability interface.

type DirectiveKind

type DirectiveKind string

DirectiveKind aligns with WorkflowStep.ResultDirective so the conductor can apply an error-driven override without translation.

const (
	// DirectiveNone is the zero value — "no opinion, defer to signal defaults".
	DirectiveNone DirectiveKind = ""

	DirectiveContinue      DirectiveKind = "continue"
	DirectiveStop          DirectiveKind = "stop"
	DirectiveRetry         DirectiveKind = "retry"
	DirectiveRetryGroup    DirectiveKind = "retry-group"
	DirectiveSkipGroup     DirectiveKind = "skip-group"
	DirectiveAwaitApproval DirectiveKind = "await-approval"
)

type Domain

type Domain string

Domain is the broad classification of an error — what kind of error it is, not where it came from. Domain is flat (no hierarchy) and lives on the row for filtering, metrics, and UI grouping.

const (
	DomainNuon       Domain = "nuon"
	DomainTerraform  Domain = "terraform"
	DomainHelm       Domain = "helm"
	DomainKubernetes Domain = "kubernetes"
	DomainAWS        Domain = "aws"
	DomainGCP        Domain = "gcp"
	DomainAzure      Domain = "azure"
	DomainRunner     Domain = "runner"
)

Standard domains. New domains are introduced as needed; kept short and stable.

type ErrorWithDirective

type ErrorWithDirective interface {
	OverrideDirective() Directive
}

ErrorWithDirective is implemented by error types that override the conductor's default directive for a failed step.

Returning Directive{Kind: DirectiveNone} (the zero value) means the type has no opinion and the signal's default applies.

type ErrorWithDocsLink interface {
	DocsURL() string
}

ErrorWithDocsLink is implemented by error types that point at a docs page.

type ErrorWithJSONSchema

type ErrorWithJSONSchema interface {
	JSONSchema() []byte
}

ErrorWithJSONSchema is implemented by error types that provide a JSON Schema for their Data payload, validated by helpers.Record() before persisting.

type ErrorWithReferences

type ErrorWithReferences interface {
	References() []Reference
}

ErrorWithReferences is implemented by error types that declare static references in addition to anything the parser attaches dynamically.

type InvocationContext

type InvocationContext struct {
	OrgID     string
	OwnerID   string
	OwnerType string

	ComponentID   string
	ComponentType string // "terraform_module", "helm_chart", "docker_build", ...

	InstallID string
	BuildID   string
	StepID    string

	// CloudPlatform is "aws" / "gcp" / "azure" if known.
	CloudPlatform string

	// Extra is a free-form bag for parser-specific hints.
	Extra map[string]any
}

InvocationContext is metadata about who/what produced the raw input. Optional and best-effort — parsers must tolerate empty fields.

type PanicHandler

type PanicHandler func(parserName string, panicValue any)

PanicHandler is invoked when a parser panics. The pipeline always treats the panicking parser's call as a non-match; the handler is for observability. nil is a no-op.

type ParseContext

type ParseContext string

ParseContext is a hierarchical, "/"-separated path identifying the producer of the raw error material being parsed. Examples:

"terraform"
"terraform/plan"
"terraform/apply"
"helm/install"
"helm/install/rbac"
"kubernetes/rollout"
"runner/job"
"build/helm"

Parsers register against one or more ParseContexts and each entry matches itself plus all descendants. ParseContext is purely a dispatch concept and is never persisted on a CompositeError row — that's what Domain is for.

type ParseInput

type ParseInput struct {
	// Raw is the bytes the producer emitted (stderr, stdout, log buffer, …).
	Raw []byte

	// ExitCode of the producing process, if applicable.
	ExitCode int

	// GoErr carries the producing Go error. By convention the caller has
	// already stripped Temporal activity / workflow error envelopes via
	// `signal.HumanError` before handing it to the pipeline — parsers
	// should treat GoErr.Error() as the user-facing message and must not
	// re-walk Temporal wrappers themselves.
	GoErr error

	// Invocation describes who/what produced Raw.
	Invocation InvocationContext
}

ParseInput is what the dispatch pipeline hands to a parser. Not every field is required to be set — a parser examines what it cares about and returns Matched=false if the input doesn't apply.

type ParseResult

type ParseResult struct {
	Matched bool
	Error   CompositeError
	Source  Source
	Refs    []Reference
	Causes  []ParseResult
}

ParseResult is what a parser returns.

Matched=false means "this parser does not claim the input"; Error/Source/Refs are ignored. Causes is a recursive list of sub-results that, when this result becomes the primary, are recorded as children in the cause graph.

type Parser

type Parser interface {
	// Name is the stable, human-readable identifier of the parser. Persisted
	// on the resulting CompositeError row's Source.ParserName for traceability.
	Name() string

	// Version is incremented when the parser's matching/extraction logic
	// changes in a way that would produce different output for the same input.
	Version() string

	// Contexts declares the ParseContext subtrees this parser opts into.
	// Each entry matches itself + descendants. An empty slice is a registration
	// error — every parser must opt in to at least one subtree.
	Contexts() []ParseContext

	// Parse examines the input and returns a ParseResult. If the input is not
	// a match, return ParseResult{Matched: false}.
	Parse(ctx context.Context, in ParseInput) ParseResult
}

Parser turns raw error material into a typed CompositeError.

Implementations live alongside their error type and register themselves into the parser catalog via init().

Parsers MUST be best-effort, fast, and side-effect-free. The pipeline recovers panics, but a parser that panics on every call drowns the failure path in noise.

type ParserLookup

type ParserLookup func(ParseContext) []Parser

ParserLookup returns parsers applicable to a ParseContext, ordered most-specific → least-specific. The catalog package implements this against its in-memory registry; tests can substitute a stub.

type Pipeline

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

Pipeline dispatches ParseInput to registered parsers and produces a PipelineResult: a primary CompositeError plus zero-or-more secondaries.

Pipeline is stateless and safe for concurrent use.

func NewPipeline

func NewPipeline(lookup ParserLookup, unknown UnknownErrorBuilder, onPanic PanicHandler) *Pipeline

NewPipeline constructs a Pipeline. Both lookup and unknown are required.

  • lookup is typically catalog.ParsersForContext.
  • unknown is typically unknown_error.Build.

func (*Pipeline) Parse

func (p *Pipeline) Parse(ctx context.Context, parseCtx ParseContext, in ParseInput) PipelineResult

Parse runs the parsers registered for parseCtx against in and returns a PipelineResult.

Dispatch rule:

  1. Walk ancestors of parseCtx, most-specific → least-specific (via the ParserLookup).
  2. At each level, run every registered parser in registration order.
  3. The first matching result becomes Primary.
  4. All other matches at any level become Secondaries.
  5. If nothing matches, fall back to the unknown error builder.

Parser panics are recovered, treated as a non-match, and reported via the onPanic handler if one was supplied.

type PipelineResult

type PipelineResult struct {
	// Primary is the headline error to attach to the owner. Always non-nil
	// after Parse() (the pipeline guarantees a fallback).
	Primary ParseResult

	// Secondaries are additional matches at any level that the caller may
	// also persist as separate rows on the same owner.
	Secondaries []ParseResult
}

PipelineResult is the output of Pipeline.Parse.

type Reference

type Reference struct {
	Type  ReferenceType  `json:"type"`
	ID    string         `json:"id,omitempty"`
	Label string         `json:"label,omitempty"`
	Meta  map[string]any `json:"meta,omitempty"`
}

Reference points at another entity (or a URL) the UI can dereference at read time. Meta is renderer-specific (e.g. {"start_line": 1421}).

type ReferenceType

type ReferenceType string

ReferenceType enumerates the kinds of things a CompositeError can point at for dynamic resolution at read time. Lets the persisted error stay small while letting the UI dereference bulk material lazily.

const (
	RefTypeLogStream                ReferenceType = "log_stream"
	RefTypeRunnerJobExecution       ReferenceType = "runner_job_execution"
	RefTypeRunnerJobExecutionResult ReferenceType = "runner_job_execution_result"
	RefTypeTerraformPlanResult      ReferenceType = "terraform_plan_result"
	RefTypeWorkflowStep             ReferenceType = "workflow_step"
	RefTypeInstallDeploy            ReferenceType = "install_deploy"
	RefTypeComponentBuild           ReferenceType = "component_build"
	RefTypeDocURL                   ReferenceType = "doc_url"
	RefTypeRunbookURL               ReferenceType = "runbook_url"
)

type References

type References []Reference

References is a slice of Reference that satisfies sql.Scanner and driver.Valuer so it can be stored directly as a JSONB column on the composite_errors row.

func (References) GormDataType

func (References) GormDataType() string

func (*References) Scan

func (r *References) Scan(v any) error

func (References) Value

func (r References) Value() (driver.Value, error)

type Render

type Render struct {
	Title       string          `json:"title"`
	Summary     string          `json:"summary,omitempty"`
	Sections    []RenderSection `json:"sections,omitempty"`
	UserActions []UserAction    `json:"user_actions,omitempty"`
}

Render is the user-facing payload computed from a CompositeError's typed fields. Sections and UserActions are ordered.

type RenderSection

type RenderSection struct {
	Heading string `json:"heading"`
	Body    string `json:"body"`
}

RenderSection is a heading + markdown body. Sections typically follow a what / why / how-to-fix structure.

type Severity

type Severity string

Severity controls how the UI presents an error and whether it is treated as a hard failure. The conductor reacts to OverrideDirective(), not Severity — severity is a UX concept.

const (
	SeverityFatal   Severity = "fatal"
	SeverityError   Severity = "error"
	SeverityWarning Severity = "warning"
	SeverityInfo    Severity = "info"
)

type Source

type Source struct {
	ParserName    string `json:"parser_name,omitempty"`
	ParserVersion string `json:"parser_version,omitempty"`
	Snippet       string `json:"snippet,omitempty"`
	ExitCode      *int   `json:"exit_code,omitempty"`
	GoError       string `json:"go_error,omitempty"`
}

Source captures the parser-input snippet that produced an error, plus identifiers for the parser that classified it. Kept small (capped) so that debugging parser decisions doesn't require re-running the producing job.

func (Source) GormDataType

func (Source) GormDataType() string

func (*Source) Scan

func (s *Source) Scan(v any) error

func (Source) Value

func (s Source) Value() (driver.Value, error)

type Type

type Type string

Type is the catalog identifier for a CompositeError implementation. Stored on the persisted row and used to look up the factory in the catalog.

type UnknownErrorBuilder

type UnknownErrorBuilder func(in ParseInput) ParseResult

UnknownErrorBuilder constructs the safety-net result when no parser matches. Provided by the unknown_error builtin via NewPipeline.

type UserAction

type UserAction struct {
	Kind  UserActionKind `json:"kind"`
	Label string         `json:"label"`
	Value string         `json:"value,omitempty"` // url, snippet, command, etc.
}

UserAction is a structured CTA the UI can render as a button or chip.

type UserActionKind

type UserActionKind string
const (
	UserActionKindLink    UserActionKind = "link"
	UserActionKindCopy    UserActionKind = "copy"
	UserActionKindCommand UserActionKind = "command"
)

Directories

Path Synopsis
Package catalog is the in-memory registry for CompositeError types and Parsers, populated via init() blocks in per-type packages.
Package catalog is the in-memory registry for CompositeError types and Parsers, populated via init() blocks in per-type packages.
Package unknown is the always-last fallback CompositeError type, shipped as a builtin so that every recorded incident has a typed, catalog-registered representation even when no parser matches.
Package unknown is the always-last fallback CompositeError type, shipped as a builtin so that every recorded incident has a typed, catalog-registered representation even when no parser matches.

Jump to

Keyboard shortcuts

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