Help us shape the future of Go! Take the 2026 Go Developer Survey

provenance

package
v0.5.0 Latest Latest
Warning

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

Go to latest
Published: Jun 18, 2026 License: MIT Imports: 6 Imported by: 0

Documentation

Overview

Package provenance is the pure-Go leaf that owns the env-debug data model and its human/JSON rendering. It imports NEITHER compose-go NOR internal/engine — engine imports IT for the shared types, so this package stays a fast, dependency-light leaf (the CI seam check guards that compose-go never leaks in).

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func RenderGapReportHuman

func RenderGapReportHuman(w io.Writer, gr GapReport, s Styler)

RenderGapReportHuman writes a human gap report: one ⚠ line per gap site plus a red summary, or a single green ok line when clean. nil styler => plain.

func RenderGapReportJSON

func RenderGapReportJSON(w io.Writer, gr GapReport) error

RenderGapReportJSON writes the gap report as indented JSON (never styled).

func RenderHuman

func RenderHuman(w io.Writer, r Report, o HumanOpts)

RenderHuman writes the selected view as plain (or styled) text. The styler is resolved once (nil ⇒ plain) and threaded into each view.

func RenderJSON

func RenderJSON(w io.Writer, r Report) error

RenderJSON writes the whole Report as indented JSON.

Types

type Effect

type Effect struct {
	Service  string `json:"service"`
	Field    string `json:"field"`
	Resolved string `json:"resolved"`
	Gap      bool   `json:"gap"` // ${VAR} is env_file-only -> falls back at the run
}

Effect is one place a variable's ${VAR} reference took effect in the compose model: the service, the dotted+[i] field path, and the resolved string.

Resolved is the REAL run value — the value the live `docker compose` run would interpolate against the Layer-1-only env. A var defined ONLY in a service env_file: resolves to its `:-default`/empty fallback here (Gap=true), because service env_files are runtime-only and never feed interpolation (v3, #3435).

type EnvEntry

type EnvEntry struct {
	Key    string `json:"key"`
	Value  string `json:"value"`
	Source Source `json:"source"`
}

EnvEntry is one key in a service's effective environment with its source (C).

type GapReport

type GapReport struct {
	Gaps  []GapSite `json:"gaps"`
	Count int       `json:"count"`
}

GapReport is the gap-report projection of a Report: every gap site + a count.

func CollectGaps

func CollectGaps(r Report) GapReport

CollectGaps projects a Report into its gap sites. Order is deterministic: var name, then the engine-sorted Effect order (service, field). A gap site is every Effect of a var whose Gap is true (engine already set Gap = referenced && !InChain && len(RuntimeDefs)>0, internal/engine/provenance.go:424).

type GapSite

type GapSite struct {
	Var      string `json:"var"`
	Service  string `json:"service"`
	Field    string `json:"field"`
	Fallback string `json:"fallback"`
	Fix      string `json:"fix"`
}

GapSite is one place an env_file:-only ${VAR} reaches the compose model and so falls back at the real run (the #3435 gap). Field names mirror Effect (service/field); Fallback is the value the run interpolates (Effect.Resolved, human-normalized via stripVarPrefix).

type HumanOpts

type HumanOpts struct {
	Trace     string // non-empty: single-var trace (A + B-lite)
	Effective bool   // per-service env (C)
	Service   string // filter Effective to one service
	Chain     bool   // Layer-1-only list (default view) — renders Report.ChainFiles
	Files     bool   // two-group view: interpolation (Report.Files) + runtime-only (Report.Services)
	Value     string // non-empty: print the winning value only
	Overview  bool   // per-file layering overview (raw values, +/~/· markers) — renders Report.Layers

	// Header inputs for the --overview mode (best-effort; from chain.Resolve + cmd).
	ComposeEnv       string // resolved CENVKIT_ENV value
	ComposeEnvSource string // where it came from: "shell" | ".env" | "default"
	ProjectDir       string // project dir (basename used as the overview title)

	Style Styler // optional color/formatting; nil ⇒ plain (byte-identical)
}

HumanOpts selects which view RenderHuman emits. Exactly one mode is active at a time; the switch in RenderHuman picks the first set field in precedence order.

type OverviewEntry

type OverviewEntry struct {
	Key      string `json:"key"`
	RawValue string `json:"raw_value"` // literal as written; ${...} unexpanded
}

OverviewEntry is one KEY=VALUE line of a file, captured LITERALLY in declaration order for the --overview lens: RawValue is exactly as written, with ${...} left UNexpanded (resolved values are --effective's job). Sourced from a thin ordered line read, NOT compose-go's dotenv parser (which is unordered and expands refs).

type OverviewLayer

type OverviewLayer struct {
	File    string          `json:"file"`              // abs path, or "(inline environment:)"
	Layer   string          `json:"layer"`             // "layer1" | "env_file" | "environment"
	Service string          `json:"service,omitempty"` // "" for chain; service name for runtime layers
	Entries []OverviewEntry `json:"entries"`           // declaration order
}

OverviewLayer is one file (or the inline-environment pseudo-layer) contributing to the layering overview, with its raw entries in declaration order. Layers are ordered on the Report: all Layer-1 chain files (chain order) first, then per active service (sorted) its env_file: layers (declared order) followed by its inline-environment layer. The +/~/· markers are NOT stored — they are derived at render time from an accumulator walk.

type Report

type Report struct {
	Files      []string            `json:"files"`              // new COMPOSE_ENV_FILES order (Layer-1 ONLY; == ChainFiles)
	ChainFiles []string            `json:"chain_files"`        // Layer-1 chain order (kept for the --chain path; D2)
	Vars       map[string]VarTrace `json:"vars"`               // A + B-lite + gap (InChain/RuntimeDefs/Gap)
	Services   []ServiceEnv        `json:"services,omitempty"` // C (also the runtime-only group for --files)
	Layers     []OverviewLayer     `json:"layers,omitempty"`   // ordered raw per-file layers for --overview (populated only when WantLayers)
}

Report is the whole env-debug picture: the ordered COMPOSE_ENV_FILES, the per-variable traces (A + B-lite), and the per-service effective env (C, empty in chain-only mode).

Post-v3 (Layer-2 debug-only): Files is the new COMPOSE_ENV_FILES = Layer-1 ONLY, so Files == ChainFiles by construction. Both fields are retained (D2): the runtime-only Layer-2 set the `--files` two-group view shows comes from Services (service `env_file:` paths), NOT from Files. `--chain` (and the bare default view) renders ChainFiles — secrets stay last WITHIN the Layer-1 chain (acceptance TestScenario12 [12.4]).

type ServiceEnv

type ServiceEnv struct {
	Service  string     `json:"service"`
	Entries  []EnvEntry `json:"entries"`
	EnvFiles []string   `json:"env_files,omitempty"` // declared env_file: paths, declared order (runtime-only; for --files)
}

ServiceEnv is a service's effective environment with per-key sources (C).

EnvFiles is the service's DECLARED `env_file:` paths in declared order — the runtime-only set the `--files` two-group view renders. It is kept separate from Entries because Entries' per-key Source can be rewritten to "(inline environment:)" when an inline `environment:` key shadows an env_file key; if EVERY key of a file is overridden, that file would vanish from an entries-derived list. Declaring the paths here keeps `--files` faithful to what the service actually loads at runtime, regardless of inline overrides.

type ServiceVal

type ServiceVal struct {
	Service string `json:"service"`
	File    string `json:"file"`
	Value   string `json:"value"`
}

ServiceVal is one service `env_file:` definition of a variable: the service it is declared under, the env_file path, and the value. It is gap EVIDENCE — runtime-only (per the service's container), NOT part of the interpolation env.

type Source

type Source struct {
	File  string `json:"file"`
	Layer string `json:"layer"` // layer1 | layer2 | env_file | environment
}

Source identifies where a value came from: a concrete file (or a synthetic "(environment)" / "(inline environment:)" marker) and which layer set it.

type Styler

type Styler interface {
	Header(s string) string  // section headers — bold cyan
	MarkerNew() string       // "+" new — green
	MarkerOverride() string  // "~" override — yellow
	MarkerRepeat() string    // "·" repeat — dim
	Arrow() string           // "→" between old/new — dim
	Key(s string) string     // KEY name — bold
	Value(s string) string   // a value — normal/readable
	Old(s string) string     // the shadowed (old) value — dim
	Path(s string) string    // file path — cyan
	Service(s string) string // service name — bold magenta
	SourceLabel(s string) string
	Gap(s string) string     // gap line body — red
	GapName(s string) string // the gapped var name — bold red
	Hint(s string) string    // a dim advisory line (e.g. empty-chain hint) — dim
	Ok(s string) string      // validate ok — green
	Fail(s string) string    // validate fail — red
	Created(s string) string // init created — green
	Skipped(s string) string // init skipped — dim
	ErrorMsg(s string) string
}

Styler renders semantic elements with optional color/formatting. It is defined HERE (not in internal/style) so internal/provenance imports NO styling library — the lipgloss-backed implementation lives in internal/style and is injected via HumanOpts.Style. A nil Style falls back to plainStyler (byte-identical plain), so callers that don't set it (and the existing render tests) are unchanged.

Methods either style a passed string (Header(s) → styled s) or, for the markers and arrow which have no payload, return the styled glyph (MarkerNew() → "+").

type VarTrace

type VarTrace struct {
	Name        string       `json:"name"`
	Value       string       `json:"value"`
	Winner      Source       `json:"winner"`
	Overridden  []Source     `json:"overridden,omitempty"`
	Effects     []Effect     `json:"effects,omitempty"`
	InChain     bool         `json:"in_chain"`               // resolvable from the interpolation (Layer-1 + shell) env?
	RuntimeDefs []ServiceVal `json:"runtime_defs,omitempty"` // service env_file: defs (runtime-only; gap evidence)
	Gap         bool         `json:"gap"`                    // referenced && !InChain && len(RuntimeDefs)>0
}

VarTrace is the full story for one variable: its winning value + source, the sources it overrode (A), where it took effect in the compose model (B-lite), and — post-v3 — whether it is a runtime-vs-interpolation gap.

Value/Winner/Overridden attribute over the INTERPOLATION env only (Layer-1 chain + shell overlay). A var defined only in a service env_file: has no chain winner (empty Winner) and InChain=false; its env_file definitions live in RuntimeDefs as gap evidence.

Jump to

Keyboard shortcuts

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