check

package
v0.3.0 Latest Latest
Warning

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

Go to latest
Published: Aug 25, 2026 License: MIT Imports: 11 Imported by: 0

Documentation

Overview

Package check implements the backstop code check validation engine.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func DetermineExitCode

func DetermineExitCode(result *Result, configErr error, flagConflict bool) int

DetermineExitCode computes the exit code from result state. Exit code 2 (config error or flag conflict) takes precedence over 1 (violations).

func FormatResult

func FormatResult(result *Result, mode OutputMode) (string, error)

FormatResult formats a Result for output in the given mode.

func NeverStarted added in v0.2.0

func NeverStarted(runErr error) bool

NeverStarted reports whether a run error means THE PROCESS NEVER STARTED, as opposed to started-and-exited-non-zero (ISSUE-112, widened to this shared home by ISSUE-140). It is the SINGLE authority for that question: BOTH consumers call it, because two copies are how the two branches drift apart — and they did, which is what ISSUE-140 reports.

  • cmd/backstop's gate dispatch (runFindingsEngine and runCoverageEngine): the coverage producer command is always filepath.Join(packRoot, …) and is already os.Stat-guarded, so *exec.Error is UNREACHABLE on that branch and a narrow check there would be greenable only by a stub runner — i.e. vacuously.
  • pkg/packval's fixture executor (DefaultExecutor.RunEngine, behind `backstop pack test` / `pack check`): its command comes from binding.Command, which is pack-declared DATA that may carry a path separator, so the path-ful shape is an ordinary pack declaration rather than an edge case.

It must NOT be replaced by `runErr != nil`. A rule-fed findings engine exits non-zero precisely WHEN it reports findings, so treating every run error as fatal would red the gate on every real finding. A started process reports an *exec.ExitError, which is neither shape below.

"Never started" is TWO Go types because exec.Command reaches LookPath only for a BARE command name (filepath.Base(name) == name):

  • *exec.Error — a bare name that LookPath could not resolve.
  • *fs.PathError with Op == "fork/exec" — a PATH-FUL command that could not be exec'd: absent, not executable, or carrying a bad interpreter line. Such a command never consults LookPath, so it can never produce an *exec.Error.

It keys on Op, never on the errno (ENOENT / EACCES / ENOEXEC), which would bake OS knowledge into a thin executor. It matches via errors.As so a wrapped error still classifies.

func ResolveScope

func ResolveScope(mode ScopeMode, filePath string) ([]string, []string, error)

ResolveScope resolves the file list for a given scope mode. It uses a DefaultGitExecutor for git operations.

func ValidateBackstopDir

func ValidateBackstopDir(projectRoot string) error

ValidateBackstopDir checks that .backstop/ directory exists at the given root.

func WithoutEnvironment added in v0.3.0

func WithoutEnvironment(environment []string, names ...string) []string

WithoutEnvironment returns a fresh environment with every entry whose key exactly matches one of names removed. Unrelated entries retain their order.

Types

type CheckType

type CheckType int

CheckType represents a validation pass type. It is the gate's neutral pass-identity vocabulary, stamped onto findings by the LIVE SARIF parser (ParsePackFindings → CheckTypeFindings). The file-extension routing manifest that once carried baked stack knowledge (a hard-coded extension list) was deleted with the in-process check engine (ISSUE-018).

const (
	// CheckTypeLint runs the lint pass.
	CheckTypeLint CheckType = iota
	// CheckTypeBuild runs the build pass.
	CheckTypeBuild
	// CheckTypeTest runs the test pass.
	CheckTypeTest
	// CheckTypeFindings is the tool-neutral rule-fed findings pass (fed by a
	// pack engine such as semgrep or ast-grep). The gate-type identity is
	// neutral; the engine is a pack detail, never baked into this name.
	CheckTypeFindings
)

func (CheckType) String

func (ct CheckType) String() string

String returns the string representation of a CheckType.

type CommandRunner

type CommandRunner interface {
	// Run returns combined stdout+stderr, used by the build/test executors
	// whose violation messages may legitimately include stderr.
	Run(ctx context.Context, name string, args ...string) ([]byte, error)
	// RunStdout returns ONLY stdout, uncontaminated by stderr (REQ-009). The
	// engine dispatch SARIF path uses it so a tool's stderr banner/progress
	// cannot corrupt the SARIF bytes on stdout.
	RunStdout(ctx context.Context, name string, args ...string) ([]byte, error)
}

CommandRunner abstracts external command execution so executors are unit-testable against fixture output without shelling out to live tools.

pkg/check must not depend on pkg/gate, so this is a local equivalent of the CommandRunner / ExecCommandRunner pair in pkg/gate/step_coverage.go.

type ConfigError

type ConfigError struct {
	Message string
}

ConfigError signals a hard stop — exit code 2. The cmd layer switches on *ConfigError to surface ExitConfigError; it must never be silently swallowed.

func (*ConfigError) Error

func (e *ConfigError) Error() string

type CoverageRecord

type CoverageRecord struct {
	// Path is the toolchain-declared FILE path (file granularity; no package noun).
	Path string `json:"path"`
	// Covered is the raw count of covered units (the gate computes the percentage).
	Covered int `json:"covered"`
	// Total is the raw count of measurable units. Total==0 ⇒ N/A (no executable
	// lines), preserved faithfully and never coerced to a 0% value (REQ-004).
	Total int `json:"total"`
	// Measured records whether the engine measured this file. The
	// measured-and-passed vs not-measured distinction is the one SARIF-as-findings
	// structurally cannot carry — the load-bearing reason coverage is not SARIF
	// (REQ-002).
	Measured bool `json:"measured"`
	// Excluded marks a pack-DECLARED exclusion (generated/vendored/no-executable).
	Excluded bool `json:"excluded"`
	// Justification is the pack-declared REASON for an exclusion, surfaced verbatim on
	// the gate's exclusion warning and never interpreted.
	//
	// It is omitempty and purely additive: every producer that predates it — including
	// packs still emitting a bare `"excluded":false` — stays wire-compatible, and the
	// gate falls back to its generic wording when it is absent. The gate deliberately
	// does NOT reject an unjustified exclusion; requiring one is a PACK's policy to
	// enforce (backstop-core's own dogfood test does exactly that), not knowledge baked
	// into the binary.
	//
	// The reason it exists at all: an exclusion with no stated reason is
	// indistinguishable from a mistake, and that is what makes the next one easy.
	Justification string `json:"justification,omitempty"`
	// Metric is the pack-declared measurement label (statement/line/branch/…). It is
	// surfaced on the report and NEVER interpreted by the gate; an empty Metric on a
	// measured record is a fail-loud error (REQ-005).
	Metric string `json:"metric"`
}

CoverageRecord is the canonical producer-side coverage record: one FILE's coverage as normalized by a coverage engine's convert (SPEC-042 REQ-003). It is the SECOND normalized output type's carrier — DISTINCT from a SARIF finding — and the SINGLE shared type that crosses the producer (dispatchPackCoverage) and the consumer (SPEC-041's coverage step); SPEC-041's drafted {Path, Pct, Measured, Excluded} is RECONCILED to this (Pct -> Covered/Total + Metric), so there is no second divergent shape and no lossy translation (REQ-006).

The record carries RAW COUNTS (Covered/Total), NEVER a pre-computed percent: the GATE computes Covered/Total >= threshold so it stays metric-BLIND and the pack bakes no percentage (REQ-003). Granularity is per-FILE — Path is the toolchain-declared file path and there is NO "package" noun (package is a Go-native concept that would re-bake language knowledge). A Total==0 record (no executable lines: pure declarations/interfaces) is N/A, never a 0%-fail (REQ-004). Metric is a PACK-DECLARED label (statement/line/branch/…) surfaced on the report but NEVER interpreted by the gate (REQ-005).

func ParsePackCoverage

func ParsePackCoverage(out []byte) ([]CoverageRecord, error)

ParsePackCoverage parses a coverage engine's normalized coverage-records JSON (the SECOND output type, DISTINCT from SARIF findings) into []CoverageRecord — the coverage analogue of ParsePackFindings (SPEC-042 REQ-001/REQ-004/REQ-005).

It is NOT SARIF and MUST NOT accept a SARIF document: the coverage-records wire shape is a JSON ARRAY of records, so a SARIF object (`{...}`, e.g. coverage tunneled through result.properties) is rejected fail-loud (CLM-007). It preserves Total==0 faithfully — no synthesized 0% (REQ-004/CLM-013) — and fail-louds on a MEASURED record with an empty Metric (REQ-005/CLM-017), an unlabeled measurement being a silent-comparison hazard.

type DefaultGitExecutor

type DefaultGitExecutor struct {
	Dir string
}

DefaultGitExecutor shells out to git for scope resolution.

func (*DefaultGitExecutor) DiffLocal

func (g *DefaultGitExecutor) DiffLocal() ([]string, error)

DiffLocal returns staged and unstaged changed files.

func (*DefaultGitExecutor) DiffNameOnly

func (g *DefaultGitExecutor) DiffNameOnly(base string) ([]string, error)

DiffNameOnly returns files changed between HEAD and the given base commit.

func (*DefaultGitExecutor) IsGitRepo

func (g *DefaultGitExecutor) IsGitRepo() bool

IsGitRepo checks if the working directory is a git repository.

func (*DefaultGitExecutor) MergeBase

func (g *DefaultGitExecutor) MergeBase(remote string) (string, error)

MergeBase finds the merge-base between HEAD and the given remote branch.

func (*DefaultGitExecutor) UntrackedFiles

func (g *DefaultGitExecutor) UntrackedFiles() ([]string, error)

UntrackedFiles returns files that are not tracked and not ignored.

type DegradedError

type DegradedError struct {
	Message string
}

DegradedError signals degraded mode — skip the check with a warning rather than failing the whole run.

func (*DegradedError) Error

func (e *DegradedError) Error() string

type ExecCommandRunner

type ExecCommandRunner struct {
	Dir string   // working directory for commands
	Env []string // environment for commands; nil inherits the parent environment
}

ExecCommandRunner is a CommandRunner that uses os/exec to run commands. Dir is set to Options.ProjectDir by callers so go build / go test resolve the project's module, mirroring pkg/gate/step_coverage.go without the gate dependency.

func (*ExecCommandRunner) Run

func (r *ExecCommandRunner) Run(ctx context.Context, name string, args ...string) ([]byte, error)

Run executes the named command with args and returns combined output. A cancelled or expired context aborts the underlying process via exec.CommandContext, so the engine's timeout-violation path fires.

func (*ExecCommandRunner) RunStdout

func (r *ExecCommandRunner) RunStdout(ctx context.Context, name string, args ...string) ([]byte, error)

RunStdout executes the named command and returns ONLY its stdout, captured via an explicit stdout buffer so stderr cannot interleave into the bytes (REQ-009 / CLM-028). On a non-zero exit it returns the stdout captured so far alongside the error so the caller can attribute the failure to the engine's output. The existing Run (CombinedOutput) method is intentionally left untouched for the build/test executors (Review Question 5).

type GitExecutor

type GitExecutor interface {
	IsGitRepo() bool
	MergeBase(remote string) (string, error)
	DiffNameOnly(base string) ([]string, error)
	DiffLocal() ([]string, error)
	UntrackedFiles() ([]string, error)
}

GitExecutor abstracts git operations for testability.

type JSONOutput

type JSONOutput struct {
	SchemaVersion string          `json:"schema_version"`
	Pass          bool            `json:"pass"`
	Violations    []JSONViolation `json:"violations"`
	Warnings      []string        `json:"warnings"`
	PassResults   []JSONPassInfo  `json:"pass_results"`
	ExitCode      int             `json:"exit_code"`
}

JSONOutput is the wire format for JSON output.

type JSONPassInfo

type JSONPassInfo struct {
	Pass       string `json:"pass"`
	Skipped    bool   `json:"skipped"`
	SkipReason string `json:"skip_reason,omitempty"`
	Violations int    `json:"violations"`
}

JSONPassInfo summarizes a pass result for JSON output.

type JSONViolation

type JSONViolation struct {
	Pass     string `json:"pass"`
	File     string `json:"file,omitempty"`
	Line     int    `json:"line,omitempty"`
	Message  string `json:"message"`
	Severity string `json:"severity,omitempty"`
	// Rule carries the structured rule identifier (e.g. a pack-namespaced
	// semgrep check_id) so the namespaced ID is not dropped from output.
	Rule string `json:"rule,omitempty"`
}

JSONViolation is the JSON representation of a single violation.

type OutputMode

type OutputMode int

OutputMode determines the output format.

const (
	// OutputModeHuman formats output for terminal reading.
	OutputModeHuman OutputMode = iota
	// OutputModeJSON formats output as structured JSON.
	OutputModeJSON
)

type Parser

type Parser func(out []byte, target CheckType) ([]Violation, error)

Parser translates raw tool output into violations for a target CheckType. The named-format registry binds each format string to one of these.

type PassResult

type PassResult struct {
	Pass       CheckType
	Violations []Violation
	Skipped    bool
	SkipReason string
}

PassResult holds the result of a single validation pass. It is retained as the carrier the output formatter (output.go) renders.

type Result

type Result struct {
	PassResults []PassResult
	Warnings    []string
	ExitCode    int
}

Result holds aggregated results from all validation passes. It is retained as the value FormatResult/DetermineExitCode (output.go) operate over.

func (*Result) AllViolations

func (r *Result) AllViolations() []Violation

AllViolations returns all violations flattened from all passes.

func (*Result) HasViolations

func (r *Result) HasViolations() bool

HasViolations returns true if any pass produced violations.

func (*Result) ViolationCount

func (r *Result) ViolationCount() int

ViolationCount returns the total number of violations across all passes.

type ScopeMode

type ScopeMode int

ScopeMode determines how the file list for validation is resolved.

const (
	// ScopeModeDiff uses git merge-base cascade to find changed files.
	ScopeModeDiff ScopeMode = iota
	// ScopeModeAll walks the entire project directory.
	ScopeModeAll
	// ScopeModeFile checks a single file.
	ScopeModeFile
)

type Violation

type Violation struct {
	Pass     CheckType
	File     string
	Line     int
	Message  string
	Severity string
	// Rule carries a structured rule identifier for the finding. For semgrep
	// findings this is the check_id, preserved verbatim including
	// pack-namespaced IDs (pack.NamespacedRuleID format, e.g.
	// "org/pack/rule-id") so violations are attributable to their source pack.
	// Empty for passes that have no per-rule identity (lint/build/test).
	Rule string
	// Fingerprint is a content-based, line-INDEPENDENT identity carried from the
	// SARIF result (partialFingerprints or region snippet). It flows to
	// gate.Violation.RegionHash so the baseline keeps multiple same-rule findings
	// in one file distinct and survives unrelated line shifts. Empty when the
	// engine emits neither, leaving the coarse message-level fallback.
	Fingerprint string
	// Properties carries the SARIF result-level `properties` object (ISSUE-062):
	// the generic structured channel a pack uses to hand the gate typed machine-data
	// (e.g. the substantiveness `func`/`symbol`) instead of tunnelling it through the
	// free-text Message. Populated by parseSarif from the result's string-valued
	// properties; nil for a finding that carries none. Flows to
	// gate.Violation.Properties additively and is DELIBERATELY excluded from baseline
	// identity, mirroring how the message-parsed contract used to be invisible to it.
	Properties map[string]string
}

Violation represents a single validation finding. It is the shared finding carrier produced by the LIVE SARIF parser (ParsePackFindings) and consumed by the gate's dispatchPackEngines path. The in-process check ENGINE that once produced it (the `backstop code check` command's Run/Engine machinery) was deleted by ISSUE-018; only the shared types and the SARIF/coverage surface survive.

func ParsePackFindings

func ParsePackFindings(out []byte) ([]Violation, error)

ParsePackFindings parses a findings engine's normalized output for the pack engine dispatch path (SPEC-031 REQ-005/REQ-006/CLM-019/CLM-036). It resolves the parser exclusively through lookupParser("sarif") — the dispatch path owns no engine enumeration and never references golangci-json/eslint-json. The returned violations are stamped with CheckTypeFindings, the pack-findings pass. A non-SARIF input fails loud via parseSarif's JSON rejection.

Jump to

Keyboard shortcuts

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