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