Skip to content

Epic: rework diagnostic pipeline, error reporting & renderer #430

Description

@pskry

Epic: rework the diagnostic pipeline, error reporting & renderer

Tracking issue for the foundation rework of how scampi talks to the
user. The current render/cli is convoluted spaghetti (imports
engine, hand-rolled concurrent TTY live-region, half-migrated dead
code), and the diagnostic pipeline needs reshaping to match how scampi
actually behaves.

The core insight: scampi is two tools wearing one CLI

  • Stream surface (long-running: apply, check) — a live narrative
    of a run, carrying three interleaved kinds of event:
    • diagnostics — warn/error as they happen
    • progress — where are we: position (3 of 10), which step is running
    • step results — what just finished and how it went
  • One-shot surface (plan, inspect, secrets list, …) — compute a
    structured value, print it once. No stream, no goroutine, no live region.

The code half-believes this already (events vs returned PlanDetail),
but the implementation smears them together. The rework makes the
duality real in the architecture.

Design decisions

  • Typed run context diagnostic.Ctx — embeds context.Context and
    carries the Emitter, so call sites thread one value, not (ctx, em)
    pairs. It is itself an Emitter (free swapped/unswapped boundary).
    Typed, not context.WithValue + SinkFrom (the v3 tribal-knowledge trap).
  • Progress becomes typed{total, completed, current}, not a text blob.
  • Step result is its own event, not folded into progress.
  • Two consumer contracts — a stream sink (owns the isolated TTY
    live-region) and plain one-shot formatters. Both consume types defined
    below engine, so neither imports it. Severs render → engine by
    construction and quarantines the concurrency.
  • Home: grow diagnostic — it already owns events, the sink, policy,
    sourcestore. Absorb model (the report/result types) into the one-shot
    surface; reassess signal's home. No new package.

Phases

  • Phase 1 — pure plumbing (no behavior change). Introduce
    diagnostic.Ctx wrapping the current Emitter; mechanically swap
    (ctx, em, …)(ctx diagnostic.Ctx, …) from the leaves up. Insulates
    every call site so the phase-2 redesign stays narrow.
    • diagnostic.Ctx type (minimal: context.Context + em)
    • linker swapped, green
    • engine swapped
    • cmd entrypoints build NewCtx, temporary boundary wraps removed
  • Phase 2 — the redesign, all behind Ctx: new event model (typed
    Progress, distinct Result), split stream/one-shot surfaces, decoupled
    renderer, killed/isolated concurrency.

Notes

  • The cause mechanism is live but minimal: engine/exec.go stamps
    CauseHook directly onto Change events; render/cli reads it for hook
    attribution. The old WithCause wrapper was dead (no callers) and was
    kept out of Ctx. Phase 2 decides whether hook-originated errors also
    want attribution (today only Changes get it).

Metadata

Metadata

Assignees

Labels

Type

No type

Fields

No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions