affected

package
v0.6.0 Latest Latest
Warning

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

Go to latest
Published: Sep 22, 2026 License: AGPL-3.0, AGPL-3.0-or-later Imports: 19 Imported by: 0

Documentation

Overview

Package affected selects the verification units reachable from a dirty worktree.

The package is language-agnostic by construction. A language participates by implementing Language; selection, witnesses, exclusion certificates, and widening are then computed identically for every plugin, so adding a language cannot change what a selection means.

This package performs no execution. It observes source text and a bounded Git status, and produces a plan. A plan is an observation about the dependency graph it names, never a claim that unselected tests cannot fail.

Index

Constants

View Source
const (
	// MaxStatusBytes bounds one status capture, including the overflow byte.
	MaxStatusBytes = 8 << 20
	// StatusDeadline is the fixed capture deadline.
	StatusDeadline = 10 * time.Second
)
View Source
const (
	WitnessDirectSource = "DIRECT_SOURCE_CHANGE"
	WitnessDirectTest   = "DIRECT_TEST_CHANGE"
	WitnessDependency   = "DEPENDENCY_PATH"
)

Witness kinds. Every selected unit names exactly one machine-checkable witness, per LPCV-V0-013.

View Source
const (
	ExcludedNoDependencyPath                 = "NO_DEPENDENCY_PATH_TO_DIRTY_UNIT"
	ExcludedDirtyGoPathMayBeDeletedOrRenamed = "UNINDEXED_DIRTY_GO_PATH_MAY_BE_DELETED_OR_RENAMED"
)

Exclusion reasons. Every eligible unit that is not selected carries one, per LPCV-V0-014.

View Source
const (
	// UnknownUnindexedSourcePath reports a dirty path a language plugin claims
	// as its own source text but which no unit declares. A new file, a rename,
	// or an excluded build variant produces this.
	UnknownUnindexedSourcePath = "UNINDEXED_SOURCE_PATH"
	// UnknownUnownedDirtyPath reports a dirty path no plugin claims. Build
	// configuration, generators, fixtures, and data files land here: they may
	// affect any unit, so the exclusion set cannot be justified.
	UnknownUnownedDirtyPath = "UNOWNED_DIRTY_PATH"
	// UnknownLanguageFrontier reports that a plugin could not resolve part of
	// its own graph exactly.
	UnknownLanguageFrontier = "LANGUAGE_FRONTIER"
)

Widening reasons. Any of these makes the plan's scope UNKNOWN, per LPCV-V0-016: uncertainty widens rather than disappearing.

View Source
const (
	ScopeBounded = "BOUNDED"
	ScopeUnknown = "UNKNOWN"
)

Scope axes, per the LPCV result-axis rule. Selection produces scope only; execution status and currency belong to the runtime provider and the caller.

View Source
const (
	// MaxWalkEntries bounds one repository walk.
	MaxWalkEntries = 400_000
	// MaxIncludedDirectoryEntries bounds each language-opted directory subtree.
	MaxIncludedDirectoryEntries = 20_000
	// MaxSourceBytes bounds one source file a plugin may read for imports.
	MaxSourceBytes = 4 << 20
)
View Source
const MaxPathsPerUnit = 20_000

MaxPathsPerUnit bounds the file lists carried by one unit.

View Source
const MaxUnits = 200_000

MaxUnits bounds one graph so a pathological tree cannot exhaust memory.

Variables

View Source
var (
	// ErrStatusUnavailable reports that the worktree status could not be
	// observed at all.
	ErrStatusUnavailable = errors.New("affected: worktree status is unavailable")
	// ErrStatusOverflow reports a status larger than the fixed bound. A
	// truncated status would understate the dirty set, so it fails closed.
	ErrStatusOverflow = errors.New("affected: worktree status exceeds the byte bound")
	// ErrStatusMalformed reports a status Corvint could not decode exactly.
	ErrStatusMalformed = errors.New("affected: worktree status is malformed")
)
View Source
var (
	// ErrInvalidUnit reports a unit that is not in canonical form.
	ErrInvalidUnit = errors.New("affected: unit is not canonical")
	// ErrDuplicateUnit reports two units sharing one identity.
	ErrDuplicateUnit = errors.New("affected: duplicate unit identity")
	// ErrDuplicateOwner reports one path claimed by two units.
	ErrDuplicateOwner = errors.New("affected: duplicate path ownership")
	// ErrInvalidLanguage reports a plugin that broke the seam contract.
	ErrInvalidLanguage = errors.New("affected: language plugin is invalid")
)
View Source
var ErrWalkLimit = errors.New("affected: repository walk exceeds its bound")

ErrWalkLimit reports a repository larger than the fixed walk bound.

View Source
var ErrWalkUnreadable = errors.New("affected: source walk hit an unreadable entry")

ErrWalkUnreadable reports a directory or entry the walk could not read.

View Source
var ErrWalkUnrepresentable = errors.New("affected: source walk hit a file path no unit can name")

ErrWalkUnrepresentable reports an accepted file whose repository-relative path is no canonical unit path, so no unit could name it.

View Source
var SkippedDirectories = map[string]bool{
	".git": true, ".hg": true, ".svn": true,
	"node_modules": true, "vendor": true, "__pycache__": true,
	".venv": true, "venv": true, ".tox": true, ".mypy_cache": true,
	".pytest_cache": true, ".ruff_cache": true, "site-packages": true,
	"dist": true, "build": true, "target": true, ".idea": true, ".vscode": true,
}

SkippedDirectories are not descended by SourceFiles. A language plugin may explicitly admit names that can hold its first-party source through SourceFilesIncluding.

Functions

func DecodeNameList

func DecodeNameList(raw []byte) ([]string, error)

DecodeNameList parses a NUL-delimited path list such as `git diff --name-only -z` emits, admitting only valid relative paths.

func DecodeStatus

func DecodeStatus(raw []byte) ([]string, error)

DecodeStatus parses the NUL-delimited porcelain v1 stream.

Each record is two status codes, a space, and the path. A rename or copy record is followed by a second NUL-terminated field holding the original path; both paths enter the dirty set, because a rename changes the unit that lost the file as well as the one that gained it.

It is exported because an authority that already captured the same status bytes must decode them the same way this package would. Sharing the decoder is what makes a published dirty set and a locally captured one the same object rather than two implementations that can drift apart on renames.

func DirectoriesOf

func DirectoriesOf(paths []string) []string

DirectoriesOf returns the sorted set of directories containing the paths. It is the cheap invalidation key for plugins whose unit is a directory.

func DirtyPaths

func DirtyPaths(ctx context.Context, gitExecutable, root string) ([]string, error)

DirtyPaths captures the exact repository-relative path set Git reports as changed in the worktree at root.

The capture fails closed: overflow, deadline, a Git failure, or an undecodable record returns an error rather than a short list that would silently narrow later selection. Ignored paths are deliberately not enumerated; they are unobserved, not proven absent.

This function reads Git's report only. It never opens a worktree file.

func DirtyPathsFor

func DirtyPathsFor(ctx context.Context, gitExecutable, root string, observation *Observation) ([]string, error)

DirtyPathsFor returns the dirty set selection consumes, preferring an existing observation over capturing one.

The observation is normalized, not re-derived: it is already the decoded output of a status capture that the observing authority bound its identity to. When none is offered, this falls back to DirtyPaths and its own fail-closed capture, so the selector still works standalone.

func NormalizePaths

func NormalizePaths(values []string) []string

NormalizePaths sorts and deduplicates a dirty path set and drops anything not in canonical repository-relative form.

func RangePaths

func RangePaths(ctx context.Context, gitExecutable, root, base string) ([]string, error)

RangePaths captures the exact repository-relative path set whose content differs between the committed tree at base and the committed tree at HEAD.

It is the committed counterpart of DirtyPaths and shares its bounds and its fail-closed rule: overflow, deadline, a Git failure, or an undecodable name returns an error, never a short list. Renames are not detected, so a moved file enters the set under both its old and new path, exactly as the status decoder admits both sides of a rename record. The diff compares two trees; it never reads the worktree.

func ReadSource

func ReadSource(root, relative string) ([]byte, error)

ReadSource reads one repository-relative source file under root, bounded by MaxSourceBytes. A file at or over the bound is refused rather than truncated, because a truncated read would silently drop import edges.

func SourceFiles

func SourceFiles(root string, accept func(name string) bool) ([]string, error)

SourceFiles walks root and returns every repository-relative file path whose name satisfies accept, sorted.

Directories in SkippedDirectories and directories whose name begins with "." are not descended. Symbolic links are never followed: a link is reported as the link itself and, because no plugin accepts a link as source text, it is simply not a unit member.

func SourceFilesIncluding

func SourceFilesIncluding(root string, accept func(name string) bool, includedDirectories ...string) ([]string, bool, error)

SourceFilesIncluding is SourceFiles with explicit language-owned exceptions to SkippedDirectories. Each exception applies at every depth.

func Union

func Union(dirty []string, overlay Overlay) []string

Union merges a Git-observed dirty set with an accepted overlay into the single sorted path set selection consumes.

Selection is identity-free at this layer: an overlaid path is dirty whether or not Git already reported it, so a keystroke in an otherwise clean file selects exactly what saving that file would have selected.

func ValidRelativePath

func ValidRelativePath(value string) bool

ValidRelativePath reports whether value is the canonical repository-relative form this package accepts everywhere: non-empty, valid UTF-8, slash separated, no leading slash, no "." or ".." component, and no empty component.

Types

type Exclusion

type Exclusion struct {
	UnitID       string `json:"unitId"`
	Reason       string `json:"reason"`
	Universe     string `json:"universe"`
	Invalidation string `json:"invalidation"`
}

Exclusion is a bounded certificate for one eligible unit the plan did not select. It names the inspected universe and the condition that invalidates it. It is never a claim that the unit's tests cannot fail.

type Graph

type Graph struct {
	// contains filtered or unexported fields
}

Graph is the composed dependency graph over every participating language.

A graph is immutable once built. Its digest covers every unit, every edge, and every frontier reason, so two graphs with the same digest select identically and a plan can name the exact universe it inspected.

func Build

func Build(root string, languages ...Language) (*Graph, error)

Build composes one graph from every supplied language plugin.

Plugins are observed in the order given but the resulting graph is order-independent: identities, paths, and edges are all canonically sorted before the digest is taken.

func (*Graph) Canonical

func (graph *Graph) Canonical() ([]byte, error)

Canonical returns the deterministic JSON body the digest is taken over. It is the persistable form: a graph rebuilt from repository authority produces byte-identical output.

func (*Graph) Digest

func (graph *Graph) Digest() string

Digest is the canonical identity of this graph.

func (*Graph) Frontier

func (graph *Graph) Frontier() []string

Frontier names everything no plugin could resolve exactly, sorted.

func (*Graph) Languages

func (graph *Graph) Languages() []string

Languages names the participating plugins, sorted.

func (*Graph) OwnerOf

func (graph *Graph) OwnerOf(path string) (string, bool)

OwnerOf returns the unit that declares a repository-relative path.

func (*Graph) Unit

func (graph *Graph) Unit(id string) (Unit, bool)

Unit returns one unit by identity.

func (*Graph) UnitIDs

func (graph *Graph) UnitIDs() []string

UnitIDs lists every unit identity, sorted.

type Language

type Language interface {
	// Name is the plugin's namespace. Every unit it returns has the identity
	// prefix Name() + ":".
	Name() string
	// Owns reports whether a repository-relative path is source text this
	// plugin is responsible for. A dirty path owned by no plugin widens scope.
	Owns(path string) bool
	// Units observes the repository rooted at the absolute path root.
	Units(root string) (Result, error)
}

Language is the seam a second language plugs into.

A plugin observes source text only. It must not execute the repository, shell out to a package manager, or read outside root.

type Observation

type Observation struct {
	Paths []string
}

Observation is a dirty path set some other component already observed, in the same repository, from the same Git status bytes this package would have captured itself.

It exists so a caller that holds an authority observation does not observe the worktree a second time. A worktree is mutable: two captures are two different facts, and a plan built from the second cannot be attributed to the identity recorded from the first. A nil *Observation means no such observation is available, which is distinct from an observation whose path set is empty because the worktree is clean.

type Overlay

type Overlay struct {
	Paths []string
}

Overlay is an accepted unsaved-editor-buffer set: repository-relative paths whose in-editor bytes differ from the worktree.

Corvint admits an overlay as a selection input only. This package never writes overlay bytes into the user's worktree; materializing them for execution is the runtime provider's job, under its own isolation.

type Plan

type Plan struct {
	GraphDigest string      `json:"graphDigest"`
	Dirty       []string    `json:"dirty"`
	Scope       string      `json:"scope"`
	Selected    []Selection `json:"selected"`
	Excluded    []Exclusion `json:"excluded"`
	Unknown     []Unknown   `json:"unknown"`
}

Plan is the selection result: what to verify, what was deliberately left out and why, and what could not be justified at all.

func Select

func Select(graph *Graph, dirty []string) Plan

Select computes the plan for one dirty path set.

The traversal is a breadth-first sweep over reverse dependency edges from every unit that directly contains a dirty path. Breadth-first order plus sorted expansion makes the witness for each reached unit the shortest chain, broken by the lexicographically smallest dirty path, so the plan is deterministic for fixed inputs (LPCV-V0-019).

A unit is traversed whether or not it declares tests; it is selected only if it declares at least one, because a unit with no tests contributes no check.

Selected units are emitted in the AFP-V0-007 order: witness chain length ascending, shared directory prefix with the witness's dirty path descending, unit id ascending. Exclusions stay in unit id order.

func (Plan) Canonical

func (plan Plan) Canonical() ([]byte, error)

Canonical returns the deterministic JSON body of a plan. This is the receipt payload an agent consumes and a digest may be taken over.

func (Plan) SelectedTests

func (plan Plan) SelectedTests() []string

SelectedTests flattens the plan's selected tests into one sorted path list.

type Result

type Result struct {
	Units    []Unit
	Frontier []string
}

Result is what one Language plugin observed for a repository.

Frontier carries the reason codes for everything the plugin could not resolve exactly. A non-empty frontier widens the plan's scope to UNKNOWN rather than silently narrowing selection.

type Selection

type Selection struct {
	UnitID  string   `json:"unitId"`
	Tests   []string `json:"tests"`
	Witness Witness  `json:"witness"`
}

Selection is one unit whose tests the plan requires.

type Unit

type Unit struct {
	ID      string   `json:"id"`
	Sources []string `json:"sources"`
	Tests   []string `json:"tests"`
	Imports []string `json:"imports"`
}

Unit is one language-agnostic verification unit: the smallest thing a runtime provider can be asked to verify. A Go package, a Python module, and a JavaScript file are all units.

Every path is repository-relative, slash-separated, sorted, and unique. Imports name other Unit identities; an import that a plugin could not resolve to a repository unit is omitted here and reported through Result.Frontier.

type Unknown

type Unknown struct {
	Reason string `json:"reason"`
	Detail string `json:"detail"`
}

Unknown is one widening reason together with the input that raised it.

type Witness

type Witness struct {
	Kind      string   `json:"kind"`
	DirtyPath string   `json:"dirtyPath"`
	Via       []string `json:"via"`
}

Witness is the reason one unit entered the plan.

Via is the dependency chain from the unit that directly contains DirtyPath to the selected unit, inclusive at both ends. For a direct change it holds one element.

Directories

Path Synopsis
Package dotnet is the C# implementation of the affected-selection language seam.
Package dotnet is the C# implementation of the affected-selection language seam.
Package golang is the Go implementation of the affected-selection language seam.
Package golang is the Go implementation of the affected-selection language seam.
Package kotlin is the Kotlin/JVM implementation of the affected-selection language seam.
Package kotlin is the Kotlin/JVM implementation of the affected-selection language seam.
Package python is the Python implementation of the affected-selection language seam.
Package python is the Python implementation of the affected-selection language seam.
Package ruby is the Ruby implementation of the affected-selection language seam.
Package ruby is the Ruby implementation of the affected-selection language seam.
Package rust is the Rust implementation of the affected-selection language seam.
Package rust is the Rust implementation of the affected-selection language seam.
Package swift is the Swift implementation of the affected-selection language seam.
Package swift is the Swift implementation of the affected-selection language seam.
Package typescript is the JavaScript and TypeScript implementation of the affected-selection language seam.
Package typescript is the JavaScript and TypeScript implementation of the affected-selection language seam.

Jump to

Keyboard shortcuts

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