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
- func CapSnippet(s string) string
- type CompositeError
- type Directive
- type DirectiveKind
- type Domain
- type ErrorWithDirective
- type ErrorWithDocsLink
- type ErrorWithJSONSchema
- type ErrorWithReferences
- type InvocationContext
- type PanicHandler
- type ParseContext
- type ParseInput
- type ParseResult
- type Parser
- type ParserLookup
- type Pipeline
- type PipelineResult
- type Reference
- type ReferenceType
- type References
- type Render
- type RenderSection
- type Severity
- type Source
- type Type
- type UnknownErrorBuilder
- type UserAction
- type UserActionKind
Constants ¶
const SourceSnippetMax = 8 * 1024
SourceSnippetMax caps how much raw input we persist on a CompositeError row.
Variables ¶
This section is empty.
Functions ¶
func CapSnippet ¶
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 ¶
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 ¶
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:
- Walk ancestors of parseCtx, most-specific → least-specific (via the ParserLookup).
- At each level, run every registered parser in registration order.
- The first matching result becomes Primary.
- All other matches at any level become Secondaries.
- 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
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 ¶
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.
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 ¶
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. |