Documentation
¶
Overview ¶
Package autoupdate provides automated version checking and update application for ebuilds in the Bentoo overlay.
The package implements:
- Package configuration management via TOML files
- Version parsing from upstream sources (JSON, regex, LLM)
- Cache management for version query results
- Pending updates tracking and application. pending.json retains only items awaiting work (Pending) or post-mortem retry (Failed); a package that completes the full Apply success path is removed from pending.json so `--list` does not surface already-applied entries.
Configuration is read from overlay/.autoupdate/packages.toml which defines how to check upstream versions for each package. Local state is maintained in ~/.config/bentoo/autoupdate/ for caching and pending updates.
Response body size cap ¶
Every HTTP response body is bounded so an oversized or malicious response cannot exhaust memory. Both GET helpers cap: RetryableHTTPClient.GetWithContext and RetryableHTTPClient.GetWithHeadersContext each wrap the response body in an http.MaxBytesReader capped at httpx.MaxBodyBytes (10 MiB); a read that exceeds the cap surfaces as an error wrapping ErrResponseTooLarge. The cap on the headers variant holds even for a Range request, since a server may ignore the Range header and stream the whole body.
The LLM clients (ClaudeClient, OpenAIClient, OllamaClient) apply the same 10 MiB default to their API response bodies, but the limit is per-client and can be raised with each client's WithMaxBodyBytes option because legitimate LLM responses (notably from a local Ollama instance) can exceed 10 MiB.
Usage:
checker, err := autoupdate.NewChecker(overlayPath)
if err != nil {
log.Fatal(err)
}
batch := checker.CheckAll(false)
// batch.Items holds the successful CheckResults; batch.Failures maps a
// package name to the error that occurred; batch.ExitCode() yields the
// 0/1/2 process exit code.
Index ¶
- Constants
- Variables
- func ApplyFallbackToSchema(schema *registry.PackageConfig, fallback *FallbackSuggestion)
- func CanRevive(prov provider.Provider) error
- func ClassifyManifestFailure(distdir string, err error, expected []string, space SpaceFunc) error
- func EnhanceSchemaWithFallback(schema *registry.PackageConfig)
- func IsFallbackOrderValid(fallbacks []FallbackSuggestion) bool
- func IsValidStatus(s UpdateStatus) bool
- func PackageAtom(key string) string
- func RepairableFetchFailures(failures map[string]error) []string
- func RequiresBuildSlot(d validate.Depth) bool
- func StalePinBatch(divs []Divergence) map[string]string
- func ValidateFallbackChain(schema *registry.PackageConfig) error
- func VendorUploadAction(err error) (action string, ok bool)
- func VersionSatisfies(pin, have, want string) bool
- type AnalysisCache
- func (c *AnalysisCache) Cleanup() error
- func (c *AnalysisCache) Clear() error
- func (c *AnalysisCache) Delete(pkg string) error
- func (c *AnalysisCache) Get(pkg string) (*registry.PackageConfig, bool)
- func (c *AnalysisCache) GetEntry(pkg string) (AnalysisCacheEntry, bool)
- func (c *AnalysisCache) GetWithBypass(pkg string, bypass bool) (*registry.PackageConfig, bool)
- func (c *AnalysisCache) Len() int
- func (c *AnalysisCache) Save() error
- func (c *AnalysisCache) Set(pkg string, schema *registry.PackageConfig, url string) error
- type AnalysisCacheEntry
- type AnalysisCacheOption
- type AnalyzeOptions
- type AnalyzeResult
- type Analyzer
- func (a *Analyzer) Analyze(ctx context.Context, pkg string, opts AnalyzeOptions) (*AnalyzeResult, error)
- func (a *Analyzer) AnalyzeAll(ctx context.Context, opts AnalyzeOptions) BatchResult[AnalyzeResult]
- func (a *Analyzer) Cache() *AnalysisCache
- func (a *Analyzer) Config() *registry.PackagesConfig
- func (a *Analyzer) FetchContent(ctx context.Context, source DataSource) ([]byte, string, error)
- func (a *Analyzer) LoadAndMergeSchema(pkg string, schema *registry.PackageConfig) error
- func (a *Analyzer) OverlayPath() string
- func (a *Analyzer) SaveSchema(pkg string, schema *registry.PackageConfig) error
- type AnalyzerOption
- func WithAnalyzerCache(cache *AnalysisCache) AnalyzerOption
- func WithAnalyzerConfigDir(dir string) AnalyzerOption
- func WithAnalyzerHTTPClient(client *fetch.RetryableHTTPClient) AnalyzerOption
- func WithAnalyzerLLMClient(llm llm.LLMProvider) AnalyzerOption
- func WithAnalyzerLLMTimeout(d time.Duration) AnalyzerOption
- func WithAnalyzerLogger(l *slog.Logger) AnalyzerOption
- func WithAnalyzerOpTimeout(d time.Duration) AnalyzerOption
- func WithAnalyzerPackagesConfig(config *registry.PackagesConfig) AnalyzerOption
- func WithAnalyzerRateLimiter(limiter *fetch.RateLimiter) AnalyzerOption
- type Applier
- func (a *Applier) Apply(ctx context.Context, pkg string, compile bool) (result *ApplyResult, _ error)
- func (a *Applier) ApplyAll(ctx context.Context, updates []PendingUpdate, compile bool, concurrency int) ([]*ApplyResult, int)
- func (a *Applier) EbuildPath(pkg, version string) string
- func (a *Applier) HTTPClient() *fetch.RetryableHTTPClient
- func (a *Applier) LogsDir() string
- func (a *Applier) MarkReenabled(pkg string)
- func (a *Applier) OverlayPath() string
- func (a *Applier) Pending() *PendingList
- func (a *Applier) RequirePin(pkg, atom string) (string, bool)
- func (a *Applier) SeedFromGentoo(pkg, srcPkgDir, gentooVersion string) error
- func (a *Applier) StagingRoot() string
- func (a *Applier) Validate(ctx context.Context, pkg string, ceiling validate.Depth) validate.EbuildResult
- func (a *Applier) VendorConfig() config.VendorConfig
- type ApplierOption
- func WithAdoptedStagedTree(root string) ApplierOption
- func WithApplierBuildConcurrency(n int) ApplierOption
- func WithApplierBuildFixer(fixer fixer.BuildFixer) ApplierOption
- func WithApplierBumpReviewer(reviewer fixer.BumpReviewer) ApplierOption
- func WithApplierClean(clean bool) ApplierOption
- func WithApplierDepth(depth validate.Depth) ApplierOption
- func WithApplierDistdir(explicit, configured string) ApplierOption
- func WithApplierDistfilesCache(dir string) ApplierOption
- func WithApplierFixer(fixer fixer.ManifestFixer) ApplierOption
- func WithApplierGentooPath(dir string) ApplierOption
- func WithApplierHTTPClient(c *fetch.RetryableHTTPClient) ApplierOption
- func WithApplierIsolationProbe(fn func() (bool, string)) ApplierOption
- func WithApplierLLMConcurrency(n int) ApplierOption
- func WithApplierLogger(l *slog.Logger) ApplierOption
- func WithApplierOutcomeHint(hint func(*ApplyResult) string) ApplierOption
- func WithApplierPackagesConfig(cfg *registry.PackagesConfig) ApplierOption
- func WithApplierPendingDeleteFunc(fn func(pkg string) error) ApplierOption
- func WithApplierPendingList(pending *PendingList) ApplierOption
- func WithApplierQuietGateOutput() ApplierOption
- func WithApplierReporter(r tui.Reporter) ApplierOption
- func WithApplierRequireIsolation(require bool) ApplierOption
- func WithApplierRequireProof(require bool) ApplierOption
- func WithApplierRunAttached(fn func(cmd *exec.Cmd) ([]byte, error)) ApplierOption
- func WithApplierRunCaptured(fn func(cmd *exec.Cmd) error) ApplierOption
- func WithApplierSetVersionsFunc(fn func(overlayPath string, pins map[string]string) error) ApplierOption
- func WithApplierStagingRoot(dir string) ApplierOption
- func WithApplierValidatePolicy(policy validate.DepthPolicy) ApplierOption
- func WithApplierVendorConfig(cfg config.VendorConfig) ApplierOption
- func WithConfirmFunc(fn func(prompt string) bool) ApplierOption
- func WithExecCommand(fn func(ctx context.Context, name string, arg ...string) *exec.Cmd) ApplierOption
- func WithLogsDir(dir string) ApplierOption
- type ApplyResult
- type AuthDistfileRequest
- type AuthDistfileResult
- type BatchResult
- type CheckResult
- type Checker
- func (c *Checker) Cache() *fetch.Cache
- func (c *Checker) CheckAll(ctx context.Context, force bool) BatchResult[CheckResult]
- func (c *Checker) CheckPackage(ctx context.Context, pkg string, force bool) (*CheckResult, error)
- func (c *Checker) Config() *registry.PackagesConfig
- func (c *Checker) DisableOrphans(pkgs []string) error
- func (c *Checker) FindRevivableOrphans(ctx context.Context, prov provider.Provider) ([]ReviveCandidate, error)
- func (c *Checker) OverlayPath() string
- func (c *Checker) Pending() *PendingList
- func (c *Checker) ReviveDisabled(pkgs []string) error
- type CheckerOption
- func WithCache(cache *fetch.Cache) CheckerOption
- func WithCacheTTL(d time.Duration) CheckerOption
- func WithConcurrency(n int) CheckerOption
- func WithConfigDir(dir string) CheckerOption
- func WithFetchCache(enabled bool) CheckerOption
- func WithGentooPath(path string) CheckerOption
- func WithGitHubTokenErrorSink(sink func(error)) CheckerOption
- func WithHTTPClient(client *fetch.RetryableHTTPClient) CheckerOption
- func WithHTTPRequestTimeout(d time.Duration) CheckerOption
- func WithLLMClient(llm llm.LLMProvider) CheckerOption
- func WithLLMProviderConfigured(configured bool) CheckerOption
- func WithLogger(l *slog.Logger) CheckerOption
- func WithOpTimeout(d time.Duration) CheckerOption
- func WithPackagesConfig(config *registry.PackagesConfig) CheckerOption
- func WithPendingList(pending *PendingList) CheckerOption
- func WithPerHostConcurrency(n int) CheckerOption
- func WithProgressCallback(cb ProgressCallback) CheckerOption
- func WithRateLimiter(limiter httpRateLimiter) CheckerOption
- func WithTypeFilter(t string) CheckerOption
- type ChildRunError
- type DataSource
- type Divergence
- type DivergenceKind
- type FallbackSuggestion
- type ParserReliability
- type PendingList
- func (p *PendingList) Add(update PendingUpdate) error
- func (p *PendingList) Clear() error
- func (p *PendingList) Delete(pkg string) error
- func (p *PendingList) Get(pkg string) (*PendingUpdate, bool)
- func (p *PendingList) Has(pkg string) bool
- func (p *PendingList) Len() int
- func (p *PendingList) List() []PendingUpdate
- func (p *PendingList) ListByStatus(status UpdateStatus) []PendingUpdate
- func (p *PendingList) Save() error
- func (p *PendingList) SetStatus(pkg string, status UpdateStatus, errMsg string) error
- type PendingListOption
- type PendingUpdate
- type ProgressCallback
- type RegistryFixAttempt
- type RegistryFixStage
- type RegistryFixStatus
- type RequirementState
- type ReviveApplier
- type ReviveCandidate
- type ReviveOutcome
- type ReviveStatus
- type Reviver
- type ReviverOption
- type ScriptParser
- type SpaceFunc
- type SweepBatch
- type SweepDirPlan
- type SweepDirResult
- type SweepOption
- func WithSweepConcurrency(n int) SweepOption
- func WithSweepDistdir(explicit, configured string) SweepOption
- func WithSweepDistfilesCache(dir string) SweepOption
- func WithSweepExecCommand(fn func(ctx context.Context, name string, arg ...string) *exec.Cmd) SweepOption
- func WithSweepLogger(l *slog.Logger) SweepOption
- func WithSweepPackagesConfig(cfgs map[string]registry.PackageConfig) SweepOption
- func WithSweepReporter(r tui.Reporter) SweepOption
- type SweepPlanError
- type SweepReport
- type UpdateStatus
- type VendorOutcome
Constants ¶
const ( RequirementPresent = "present" RequirementPending = "pending" RequirementMissing = "missing" )
The three values RequirementState.State takes.
const ( // PriorityProvided is the highest priority for user-provided URLs PriorityProvided = 0 // PriorityGitHub is the priority for GitHub releases API PriorityGitHub = 10 // PriorityPyPI is the priority for PyPI API PriorityPyPI = 20 // PriorityNPM is the priority for npm registry API PriorityNPM = 20 // PriorityCrates is the priority for crates.io API PriorityCrates = 20 // PriorityHomepage is the lowest priority for generic homepage scraping PriorityHomepage = 100 )
Priority constants for data source ordering
const ( ContentTypeJSON = "application/json" ContentTypeHTML = "text/html" )
Content type constants
const ( ParserTypeJSON = "json" ParserTypeHTML = "html" ParserTypeRegex = "regex" ParserTypeLLM = "llm" )
ParserType constants for parser types
const ( // ValidationSourceStaged means the gates were paid for by an earlier run and // this one promoted that run's tree. ValidationSourceStaged = "staged" // ValidationSourceThisRun means this run ran the gates itself. ValidationSourceThisRun = "this-run" )
ValidationSource says where THIS apply's verdict came from, and it has exactly two values.
It is on the result rather than only in a log line because a fast green and a proved green look identical from the outside: an apply that took four seconds because the gates ran a year of Tuesdays ago and one that took four seconds because nothing was checked are the same four seconds. The operator is told which happened, per package.
const DefaultAnalysisCacheTTL = 24 * time.Hour
DefaultAnalysisCacheTTL is the default time-to-live for analysis cache entries (24 hours)
const DefaultApplyBuildConcurrency = 2
DefaultApplyBuildConcurrency bounds how many applies of one batch run their build gates at once. The rest of each apply — fetch, `pkgdev manifest`, staging — keeps the full --concurrency pool; only the build is narrowed, because concurrent builds contend for CPU and for space under PORTAGE_TMPDIR (60 MB measured for one gst configure).
const DefaultApplyLLMConcurrency = 2
DefaultApplyLLMConcurrency bounds how many LLM agents (manifest fixer, build fixer, bump reviewer) one Applier runs at once. Each agent is a long-lived claude child with its own token budget, so a pool of --concurrency of them would spend the budget and the provider's rate limit many times over.
const DefaultConcurrency = 10
DefaultConcurrency is the default number of packages processed in parallel when no explicit concurrency is configured. It governs both --check (CheckAll's per-package HTTP fan-out) and the --apply all worker pool. For --check, per-host rate limiting — not this number — bounds the request rate to any single provider (GitHub ~10/s, GitLab ~3/s), so the tuned limiters stay saturated. For --apply each worker runs a `pkgdev manifest` that fetches distfiles over the network and bypasses those limiters, so a moderate default keeps concurrent downloads from overwhelming a single host. 10 balances throughput against both.
const DefaultLLMTimeout = 60 * time.Second
DefaultLLMTimeout is the default per-operation timeout applied to a single LLM analysis call when no explicit timeout is configured on the Analyzer.
const DefaultOpTimeout = 30 * time.Second
DefaultOpTimeout is the default per-operation timeout applied to a single outbound HTTP fetch when no explicit timeout is configured on the Checker.
const DefaultPerHostConcurrency = 6
DefaultPerHostConcurrency caps the requests the checker has in flight to one host at a time. The rate limiter paces how often a request may START, not how many are still running: on a host that stops answering, every request it admits hangs for its whole timeout and retry budget, so a run with --concurrency 40 piles dozens of connections onto the one server that is already struggling. Six leaves GitHub — paced at ten starts a second — its throughput at normal latency.
const MaxPatternLen = 512
MaxPatternLen is the maximum allowed length, in characters, of an LLM-generated regex pattern. Patterns longer than this are rejected as a basic ReDoS prophylaxis.
const VendorGeneratedNote = "vendor tarball generated locally, not uploaded"
VendorGeneratedNote marks a check whose gates ran on a vendor tarball this run generated: a check never uploads one.
Variables ¶
var ( // ErrAnalysisCacheCorrupted is returned when the analysis cache file cannot be parsed ErrAnalysisCacheCorrupted = errors.New("analysis cache file is corrupted") // ErrAnalysisCacheMiss is returned when an analysis cache entry is not found or expired ErrAnalysisCacheMiss = errors.New("analysis cache miss") )
Error variables for analysis cache errors
var ( // ErrSchemaExists is returned when a schema already exists for a package ErrSchemaExists = errors.New("schema already exists for package") // ErrNoDataSources is returned when no data sources are found for a package ErrNoDataSources = errors.New("no data sources found for package") // ErrAnalysisFailed is returned when LLM analysis fails ErrAnalysisFailed = errors.New("LLM analysis failed") // ErrInvalidPattern is returned when an LLM-generated regex pattern is // invalid: it fails to compile, exceeds MaxPatternLen, or uses // backreferences (which RE2 does not support). ErrInvalidPattern = errors.New("invalid regex pattern") )
Error variables for analyzer errors
var ( // ErrManifestFailed is returned when the manifest command fails ErrManifestFailed = errors.New("manifest command failed") // ErrCompileFailed is returned when the compile test fails ErrCompileFailed = errors.New("compile test failed") // ErrNoPrivilegeEscalation is returned when neither sudo nor doas is available ErrNoPrivilegeEscalation = errors.New("no privilege escalation tool available (sudo or doas)") // ErrUserDeclined is returned when user declines the compile confirmation ErrUserDeclined = errors.New("user declined compile test") // ErrInvalidNewVersion is returned when the detected upstream version cannot // be coerced into a well-formed Gentoo PV (e.g. it carries a tag prefix that // survives normalization, or is not a version at all). ErrInvalidNewVersion = errors.New("invalid new version for ebuild") // ErrObsoletePending is returned (wrapped) when a pending entry no longer // matches the live overlay: the package was removed, or the overlay is // already at/beyond the target version. The entry is pruned and the outcome // is reported as obsolete, not as a failure. ErrObsoletePending = errors.New("obsolete pending entry") // ErrEbuildExists is returned when the destination ebuild a copy would write // is already present in the overlay. Overwriting it would destroy a file the // applier never authored — see copyEbuild's guard for why this is fatal // rather than a silent overwrite. ErrEbuildExists = errors.New("destination ebuild already exists") // ErrInvalidAuxValue is returned (wrapped) when a pending update's AuxValue // is outside auxValueRe. The value was scraped from an upstream page and is // written into the bash source of the new ebuild, so anything that could // close the quoted assignment or reach the shell is refused, not repaired. ErrInvalidAuxValue = errors.New("invalid aux value for ebuild") // ErrInvalidCommitHash is returned (wrapped) when a pending update's // CommitHash is not 40 lowercase hex — the only form substituteCommitHash // can find again on the next bump. ErrInvalidCommitHash = errors.New("invalid commit hash for ebuild") // ErrInvalidRequiredVersion is returned (wrapped) when a version captured // for a `requires` entry and replayed from pending.json is not a Gentoo // version as written. It is about to be written into bash, so the bump fails // before anything is staged or copied. ErrInvalidRequiredVersion = errors.New("invalid required version for ebuild") // ErrRequirementsNotCaptured marks a pending entry of a record that declares // `requires` but carries no captured version for one of them — an entry // queued before the record declared it, or by an older release. The bump // waits until `--check` captures the requirement. ErrRequirementsNotCaptured = errors.New("requirements not captured; re-run --check to capture them") // ErrRequirementPinNotFound is returned (wrapped) when the record declares a // pin the new ebuild does not carry: the registry and the ebuild disagree. ErrRequirementPinNotFound = errors.New("pinned requirement not found in ebuild") // ErrRepairPending is returned when the package's staged tree carries a // repair bundle and the run is not adopting that tree: restaging would // delete the operator's pending repair, so the package is refused instead. ErrRepairPending = errors.New("repair pending") )
Error variables for applier errors
var ( // ErrPackageNotInRegistry is returned when packages.toml holds no record // for the requested package. It is separate from ErrNoAuthFetch because the // remedies differ: one is a package this overlay does not track, the other // is a tracked package whose distfile needs no help. ErrPackageNotInRegistry = errors.New("no packages.toml record for this package") // ErrAmbiguousPackageKey is returned when the requested atom matches more // than one record — the slot and label suffixes exist precisely so one atom // can carry several entries, and guessing between them would fetch the // wrong release line in silence. ErrAmbiguousPackageKey = errors.New("the atom matches more than one packages.toml record") // ErrNoAuthFetch is returned for a record that configures no authenticated // download. Its distfile is on a mirror and `emerge` fetches it unaided, so // this is a "nothing to do here", not a failure of the download. ErrNoAuthFetch = errors.New("this package configures no authenticated distfile fetch") )
var ( // ErrFetchFailed is returned when fetching upstream version fails ErrFetchFailed = errors.New("failed to fetch upstream version") // ErrUpstreamUnreachable is wrapped beside ErrFetchFailed when the fetch // failed in transport — retries exhausted, a timeout, an open circuit // breaker — rather than on what the record asked for. Nothing in the record // is wrong, so the interactive registry repair must not offer to rewrite it. ErrUpstreamUnreachable = errors.New("upstream unreachable") // ErrRequirementUnresolved is returned (wrapped) when a record's `requires` // entry could not be captured for the detected version. The bump is held, // like an unresolved aux value: a pending entry with a partial requirement // set could not be told apart from a complete one. ErrRequirementUnresolved = errors.New("requirement unresolved") // ErrBaseVersionUnresolved is returned when an entry declares where its base // version lives (base_from, or commit_version_pattern) and that source yields // nothing. It is deliberately fatal for the check: falling back to the // ebuild's own base is what let six Khronos packages drift up to seven // releases behind while still reporting "up to date". ErrBaseVersionUnresolved = errors.New("declared base version source resolved nothing") // ErrAuxUnresolved is recorded (wrapped, on CheckResult.Error) when a package // declares aux_pattern or commit_sha_path and that value could not be // resolved. The bump is held rather than queued: applying it would ship the // previous release's value under the new version. ErrAuxUnresolved = errors.New("auxiliary value unresolved") )
Error variables for checker errors
var ( // ErrPendingCorrupted is returned when the pending file cannot be parsed ErrPendingCorrupted = errors.New("pending file is corrupted") // ErrPackageNotInPending is returned when a package is not found in pending updates ErrPackageNotInPending = errors.New("package not found in pending updates") // ErrInvalidStatusTransition is returned when an invalid status transition is attempted ErrInvalidStatusTransition = errors.New("invalid status transition") )
Error variables for pending list errors
var ( // ErrVendorGenerate marks a Go vendor tarball that could not be generated, // or a served one that could not be compared with a regenerated tree. ErrVendorGenerate = errors.New("vendor tarball generation failed") // ErrVendorStale marks a served vendor tarball whose name does not carry // the candidate's version and whose vendor/modules.txt no longer matches // the one the candidate's source produces. ErrVendorStale = errors.New("pinned vendor tarball is stale") )
var ( // ErrVendorUpload marks a generated vendor tarball whose publication was not // proved: the upload command failed or timed out, the served bytes differ // from the generated ones, or the tarball changed before it was published. ErrVendorUpload = errors.New("vendor tarball upload failed") // ErrVendorUploadPending marks a generated vendor tarball that was kept // rather than published because no upload command is configured. Its // wrapped text names the kept path and the URL it must be served from. ErrVendorUploadPending = errors.New("vendor tarball upload pending") )
var ErrBuildEnvironment = errors.New("the build failed on the environment, not on the ebuild")
ErrBuildEnvironment reports that a failed build failed on the machine rather than on the ebuild: the host is missing build dependencies, the run died before the ebuild's own phases began, the build device ran out of space, or Portage's build directory is not writable.
It is the sentinel the build-fix gate keys on, exactly as ErrManifestEnvironment is for the manifest step. An agent handed such a failure has one repair available to it — edit the ebuild — and the ebuild is not what failed, so it spends turns and money concluding that a correct file is wrong. Callers test this with errors.Is; the reason and the build's own error are always wrapped alongside, so the operator still reads what actually happened.
var ErrInvalidSweepTarget = errors.New("invalid sweep target")
ErrInvalidSweepTarget is returned for a target that is neither a category nor a "category/package" atom present in the overlay.
var ErrManifestEnvironment = errors.New("manifest step failed on the environment, not on the ebuild")
ErrManifestEnvironment reports that a failed manifest step failed on the machine, not on the ebuild: the distdir stopped being writable, its filesystem ran out of room, or the fetch left an empty artefact behind.
It is what the fixer gate keys on. An LLM fixer handed such a failure has no repair available to it — its only reachable conclusion is that SRC_URI is wrong — so it edits a correct ebuild in a repository that commits and pushes on its own. Callers test this with errors.Is; the original error is always wrapped alongside it, so the operator still reads exactly what pkgdev said.
var ErrManifestIncomplete = errors.New("manifest is missing DIST entries")
ErrManifestIncomplete is returned (wrapped) when the manifest step succeeded but the new ebuild's SRC_URI names a distfile its Manifest has no DIST line for. Such an ebuild passes every later gate that does not fetch, and fails on the user's machine with "does not have a Manifest entry" — a batch bump once shipped 76 of them.
var ErrNoLocalPackageDir = errors.New("the gentoo provider has no local package directory")
ErrNoLocalPackageDir reports that the ::gentoo provider exposes no on-disk package directory (it is not a provider.PackageDirProvider), so a revive has no base ebuild to seed from.
var ErrScriptSupportNotBuilt = errors.New(
"script parser support not built: rebuild with -tags chromedp; " +
"a Chrome or Chromium executable is required at run time")
ErrScriptSupportNotBuilt is returned when a parser="script" package is checked by a binary compiled WITHOUT the chromedp headless-browser backend. The real evaluator lives behind the `chromedp` build tag and drives a Chrome or Chromium already installed on the system; the default build ships only the testable interface so the browser dependency stays opt-in.
Functions ¶
func ApplyFallbackToSchema ¶
func ApplyFallbackToSchema(schema *registry.PackageConfig, fallback *FallbackSuggestion)
ApplyFallbackToSchema applies a fallback suggestion to a PackageConfig. It sets the FallbackParser field based on the suggestion.
func CanRevive ¶
CanRevive reports, before any other work, whether prov can seed a revive. It returns a wrapped ErrNoLocalPackageDir when prov is not a PackageDirProvider.
func ClassifyManifestFailure ¶
ClassifyManifestFailure decides whether err — the error a failed `pkgdev manifest` returned — is an environment failure or a repairable one, by checking observable state in this order (an unwritable distdir explains an empty artefact, so the cause is named before its symptom):
- distdir no longer writable (the pre-flight probe, repeated) -> environment
- free space below the floor any fetch needs -> environment
- an expected artefact present with zero length -> environment
- none of the above -> repairable
For 1-3 it returns an error wrapping both ErrManifestEnvironment and err, so the operator still reads what pkgdev printed. For 4 it returns err untouched, the same value, so a caller testing it for something else keeps finding it.
"Uncertain" means "repairable": a check that cannot be answered (an empty distdir, a failing space query, an uninspectable name) is no evidence and falls through. A nil space uses the real statfs query rather than skipping. expected holds untrusted distfile NAMES, each reduced to one filename before the join. It never panics, never logs, and writes only Probe's own file.
func EnhanceSchemaWithFallback ¶
func EnhanceSchemaWithFallback(schema *registry.PackageConfig)
EnhanceSchemaWithFallback adds fallback configuration to a schema based on its primary parser. This is called after LLM analysis to ensure fallback is always configured.
func IsFallbackOrderValid ¶
func IsFallbackOrderValid(fallbacks []FallbackSuggestion) bool
IsFallbackOrderValid checks if fallbacks are ordered by reliability. Returns true if fallbacks are in correct order (JSON > HTML > regex > LLM).
func IsValidStatus ¶
func IsValidStatus(s UpdateStatus) bool
IsValidStatus checks if a status is valid
func PackageAtom ¶
PackageAtom returns the "category/package" a package key names, dropping any ":slot" and "@label", and "" for a key that is not an atom. The --apply all planner uses it to match a pending entry against another entry's requirement.
func RepairableFetchFailures ¶
RepairableFetchFailures returns, in lexical order, the packages whose failure wraps ErrFetchFailed — the only class the registry fixer can repair. The match is errors.Is, never the error text: a failure that merely says "failed to fetch upstream version" is not repairable, and one wrapped twice or joined with another error still is.
A failure that also wraps ErrUpstreamUnreachable is excluded: a timeout or a TLS EOF is a fetch failure the record did not cause, so offering to rewrite the record would invite a distracted "y" to break an entry that was correct.
func RequiresBuildSlot ¶ added in v0.36.0
RequiresBuildSlot reports whether an apply validating at depth d must hold one of the Applier's build slots while its build gates run.
Every depth that STARTS A BUILD does: concurrent builds contend for CPU and for space under PORTAGE_TMPDIR, measured at 60 MB for one gst configure, so they are bounded below the --concurrency pool (DefaultApplyBuildConcurrency). Depths none and options only read files that are already on disk and take no slot.
func StalePinBatch ¶
func StalePinBatch(divs []Divergence) map[string]string
StalePinBatch builds the write batch, and is the ONLY place a Divergence becomes something written to the registry.
It switches on Kind because the three classes are not interchangeable, and mapping all of them to Key -> Disk corrupts the registry in two distinct ways:
- NoEbuild carries an empty Disk, so writing it would ERASE the entry's pin — and an entry with no pin blocks its whole directory's next --clean;
- UnclaimedEbuild's Key is a bare "category/package" atom, not a registry key, and the two routinely coincide (net-misc/rclone is both). Writing it would point that entry's pin at the stray ebuild the finding is asking a human to delete: the exact opposite of the intent.
A future fourth kind falls through unwritten, which is the safe direction.
func ValidateFallbackChain ¶
func ValidateFallbackChain(schema *registry.PackageConfig) error
ValidateFallbackChain validates that a schema's fallback configuration is valid. It checks that the fallback parser is different from the primary parser and that required fields are set.
func VendorUploadAction ¶ added in v0.36.0
VendorUploadAction returns the "upload <path> to <uri>" action an ErrVendorUploadPending error carries; ok is false for any other error. The error text is the single source of the kept path and the URL, so every renderer reads them from here.
func VersionSatisfies ¶
VersionSatisfies reports whether an ebuild at version have meets the pin `<pin><category>/<package>-<want>`:
- "~" — have, with its -rN revision dropped, equals want;
- "=" — have equals want exactly, revision included;
- ">=" — have compares greater than or equal to want.
Any other pin is never satisfied. It is the one implementation of the rule: the presence scan, the check report's pending state and the --apply all wave planner all call it, so the three can never disagree about a revision.
Types ¶
type AnalysisCache ¶
type AnalysisCache struct {
// Entries holds all cached analysis entries, keyed by package name
Entries map[string]AnalysisCacheEntry `json:"entries"`
// TTL is the time-to-live for cache entries (default: 24 hours)
TTL time.Duration
// contains filtered or unexported fields
}
AnalysisCache manages LLM analysis result caching with TTL-based expiration. It persists cache entries to disk and supports concurrent access. Cache is stored in ~/.config/bentoo/autoupdate/analysis_cache.json
func NewAnalysisCache ¶
func NewAnalysisCache(configDir string, opts ...AnalysisCacheOption) (*AnalysisCache, error)
NewAnalysisCache creates or loads an analysis cache from disk. If the cache file exists, it loads existing entries. If the cache file doesn't exist or is corrupted, it creates a new empty cache. The configDir should be the bentoo config directory (e.g., ~/.config/bentoo/autoupdate).
func (*AnalysisCache) Cleanup ¶
func (c *AnalysisCache) Cleanup() error
Cleanup removes all expired entries from the analysis cache. It automatically saves the cache to disk after cleanup.
func (*AnalysisCache) Clear ¶
func (c *AnalysisCache) Clear() error
Clear removes all entries from the analysis cache. It automatically saves the cache to disk after clearing.
func (*AnalysisCache) Delete ¶
func (c *AnalysisCache) Delete(pkg string) error
Delete removes a package from the analysis cache. It automatically saves the cache to disk after deletion.
func (*AnalysisCache) Get ¶
func (c *AnalysisCache) Get(pkg string) (*registry.PackageConfig, bool)
Get retrieves a cached schema if it exists and is not expired. Returns the schema and true if found and valid, nil and false otherwise.
Get also performs lazy revalidation: a cached schema whose regex pattern or XPath expression no longer passes validation (e.g. it was written by an older, less strict build) is evicted via Delete and reported as a cache miss. A miss — not an error — is the normal signal that prompts the caller to re-run analysis.
func (*AnalysisCache) GetEntry ¶
func (c *AnalysisCache) GetEntry(pkg string) (AnalysisCacheEntry, bool)
GetEntry retrieves the full analysis cache entry for a package. Returns the entry and true if found, zero value and false otherwise. This does not check TTL - use Get for TTL-aware retrieval.
func (*AnalysisCache) GetWithBypass ¶
func (c *AnalysisCache) GetWithBypass(pkg string, bypass bool) (*registry.PackageConfig, bool)
GetWithBypass retrieves a cached schema, optionally bypassing the cache. If bypass is true (--no-cache flag), always returns cache miss. Returns the schema and true if found and valid (and not bypassed), nil and false otherwise.
func (*AnalysisCache) Len ¶
func (c *AnalysisCache) Len() int
Len returns the number of entries in the analysis cache.
func (*AnalysisCache) Save ¶
func (c *AnalysisCache) Save() error
Save persists the analysis cache to disk. This is thread-safe and can be called concurrently.
func (*AnalysisCache) Set ¶
func (c *AnalysisCache) Set(pkg string, schema *registry.PackageConfig, url string) error
Set stores a schema in the analysis cache with the current timestamp. It automatically saves the cache to disk after setting.
type AnalysisCacheEntry ¶
type AnalysisCacheEntry struct {
// Schema is the cached PackageConfig generated by LLM analysis
Schema *registry.PackageConfig `json:"schema"`
// Timestamp is when this entry was cached
Timestamp time.Time `json:"timestamp"`
// URL is the URL that was analyzed to generate this schema
URL string `json:"url"`
}
AnalysisCacheEntry represents a cached LLM analysis result. It stores the generated schema, when it was cached, and the source URL.
type AnalysisCacheOption ¶
type AnalysisCacheOption func(*AnalysisCache)
AnalysisCacheOption is a functional option for configuring AnalysisCache
func WithAnalysisCacheLogger ¶
func WithAnalysisCacheLogger(l *slog.Logger) AnalysisCacheOption
WithAnalysisCacheLogger sets the logger the cache reports its diagnostics to. Nil keeps the default, which discards them.
func WithAnalysisCacheNowFunc ¶
func WithAnalysisCacheNowFunc(fn func() time.Time) AnalysisCacheOption
WithAnalysisCacheNowFunc sets a custom time function for testing
func WithAnalysisCacheTTL ¶
func WithAnalysisCacheTTL(ttl time.Duration) AnalysisCacheOption
WithAnalysisCacheTTL sets a custom TTL for the analysis cache
type AnalyzeOptions ¶
type AnalyzeOptions struct {
// URL overrides the URL for analysis
URL string
// Hint provides user guidance to the LLM
Hint string
// NoCache bypasses all caches
NoCache bool
// Force overwrites existing schema
Force bool
// DryRun shows schema without saving
DryRun bool
}
AnalyzeOptions configures the analysis behavior.
type AnalyzeResult ¶
type AnalyzeResult struct {
// Package is the full package name (category/package)
Package string
// SuggestedSchema is the schema suggested by analysis
SuggestedSchema *registry.PackageConfig
// Validated indicates if the schema was validated successfully
Validated bool
// ExtractedVersion is the version extracted using the schema
ExtractedVersion string
// EbuildVersion is the current version from the ebuild
EbuildVersion string
// Error contains any error that occurred during analysis
Error error
// DataSource is the data source used for analysis
DataSource *DataSource
// FromCache indicates if the result was from cache
FromCache bool
}
AnalyzeResult represents the result of analyzing a package.
type Analyzer ¶
type Analyzer struct {
// contains filtered or unexported fields
}
Analyzer handles package analysis and schema generation. It coordinates between ebuild metadata extraction, data source discovery, LLM analysis, and schema validation.
func NewAnalyzer ¶
func NewAnalyzer(overlayPath string, opts ...AnalyzerOption) (*Analyzer, error)
NewAnalyzer creates a new analyzer instance for the given overlay.
func (*Analyzer) Analyze ¶
func (a *Analyzer) Analyze(ctx context.Context, pkg string, opts AnalyzeOptions) (*AnalyzeResult, error)
Analyze analyzes a single package and suggests a schema. Every fetch and LLM call it makes is bounded by a child of ctx, so cancelling ctx (e.g. on SIGINT) aborts the analysis in flight.
func (*Analyzer) AnalyzeAll ¶
func (a *Analyzer) AnalyzeAll(ctx context.Context, opts AnalyzeOptions) BatchResult[AnalyzeResult]
AnalyzeAll analyzes all packages without schemas, at most 3 at a time.
It returns a BatchResult: analyzed packages land in Items, a per-package failure is recorded in Failures keyed by package name and the batch goes on. A failure to enumerate the packages is a single synthetic Failures entry, which yields a total-failure exit code.
The pool follows CheckAll's model:
- a slot is taken BEFORE a worker starts, so at most 3 analyses run at once and a package waiting for its turn holds no goroutine;
- once ctx is done, no package without a slot is started: each is recorded in Failures wrapping the context's error, and running analyses finish;
- a panic in one analysis is recovered as that package's failure ("panic: <value>"), so it neither stops the others nor crashes the process;
- Items are sorted by Package, independent of which analysis finished first.
Every write to the shared BatchResult is mutex-guarded, and it is returned only after every worker goroutine has joined (wg.Wait), so callers may safely invoke its methods (ExitCode, FormatFailures) on the returned value.
func (*Analyzer) Cache ¶
func (a *Analyzer) Cache() *AnalysisCache
Cache returns the analysis cache.
func (*Analyzer) Config ¶
func (a *Analyzer) Config() *registry.PackagesConfig
Config returns the packages configuration.
func (*Analyzer) FetchContent ¶
FetchContent fetches content from a data source (exported for testing). The fetch is bounded by ctx.
func (*Analyzer) LoadAndMergeSchema ¶
func (a *Analyzer) LoadAndMergeSchema(pkg string, schema *registry.PackageConfig) error
LoadAndMergeSchema loads existing config, adds/updates a schema, and saves. This ensures existing entries are preserved when adding new schemas.
func (*Analyzer) OverlayPath ¶
OverlayPath returns the overlay path.
func (*Analyzer) SaveSchema ¶
func (a *Analyzer) SaveSchema(pkg string, schema *registry.PackageConfig) error
SaveSchema saves a validated schema to packages.toml.
type AnalyzerOption ¶
AnalyzerOption is a functional option for configuring Analyzer.
func WithAnalyzerCache ¶
func WithAnalyzerCache(cache *AnalysisCache) AnalyzerOption
WithAnalyzerCache sets a custom analysis cache for the analyzer.
func WithAnalyzerConfigDir ¶
func WithAnalyzerConfigDir(dir string) AnalyzerOption
WithAnalyzerConfigDir sets the configuration directory for the analyzer.
func WithAnalyzerHTTPClient ¶
func WithAnalyzerHTTPClient(client *fetch.RetryableHTTPClient) AnalyzerOption
WithAnalyzerHTTPClient sets a custom HTTP client for the analyzer.
func WithAnalyzerLLMClient ¶
func WithAnalyzerLLMClient(llm llm.LLMProvider) AnalyzerOption
WithAnalyzerLLMClient sets a custom LLM client for the analyzer.
func WithAnalyzerLLMTimeout ¶
func WithAnalyzerLLMTimeout(d time.Duration) AnalyzerOption
WithAnalyzerLLMTimeout sets the per-operation timeout used to derive a child context for each LLM analysis call. A non-positive duration is rejected.
func WithAnalyzerLogger ¶
func WithAnalyzerLogger(l *slog.Logger) AnalyzerOption
WithAnalyzerLogger sets the logger the analyzer reports its diagnostics to, and hands it to the analysis cache and HTTP client the analyzer uses. Nil keeps the default, which discards them (and leaves an injected cache's or client's own logger alone).
func WithAnalyzerOpTimeout ¶
func WithAnalyzerOpTimeout(d time.Duration) AnalyzerOption
WithAnalyzerOpTimeout sets the per-operation timeout used to derive a child context for each outbound HTTP fetch. A non-positive duration is rejected.
func WithAnalyzerPackagesConfig ¶
func WithAnalyzerPackagesConfig(config *registry.PackagesConfig) AnalyzerOption
WithAnalyzerPackagesConfig sets a custom packages configuration.
func WithAnalyzerRateLimiter ¶
func WithAnalyzerRateLimiter(limiter *fetch.RateLimiter) AnalyzerOption
WithAnalyzerRateLimiter sets a custom rate limiter for the analyzer.
type Applier ¶
type Applier struct {
// contains filtered or unexported fields
}
Applier handles update application for packages. It coordinates between pending list and file system operations.
func NewApplier ¶
func NewApplier(overlayPath, configDir string, opts ...ApplierOption) (*Applier, error)
NewApplier creates a new applier instance for the given overlay. It initializes the pending list and logs directory.
func (*Applier) Apply ¶
func (a *Applier) Apply(ctx context.Context, pkg string, compile bool) (result *ApplyResult, _ error)
Apply applies a pending update for a package. It copies the ebuild to the new version and runs the manifest command. If compile is true, it also runs a compile test with elevated privileges.
The result is returned via a named value so a single deferred cleanup can observe whichever error the function ultimately surfaces (result.Error is kept in lockstep with the returned error on every path).
func (*Applier) ApplyAll ¶
func (a *Applier) ApplyAll(ctx context.Context, updates []PendingUpdate, compile bool, concurrency int) ([]*ApplyResult, int)
ApplyAll applies every pending update through the shared Applier and returns the per-package results in input order plus the number of hard failures (an Apply returning a non-nil error). It is the concurrency seam of `bentoo overlay autoupdate --apply all` (cmd/bentoo runApplyAll).
Every batch runs on a worker pool of concurrency workers, clamped to [1, len(updates)], whatever the flags or the validation depths. The expensive stages are bounded inside Apply instead (apply_slots.go): build gates by build slots, the privileged --compile step one at a time so its prompt and sudo invocation never interleave, and the LLM agents by LLM slots. The pool is safe because the Applier's pending list and reporter are mutex-guarded, each Apply works in its own package directory, and workers write distinct result indices.
Every Apply receives ctx and returns a failed result at once on a done ctx, so after a cancel each unstarted package still gets a non-nil, failed result in input order; an early break would leave nil results instead. A bump that requires another pending bump runs in a later wave (applyWaves), so its requirement gate sees what the earlier one published.
func (*Applier) EbuildPath ¶
EbuildPath returns the full path to an ebuild file in the PUBLISHED overlay, or "" when pkg is not a well-formed category/package. It delegates so that the filename an ebuild carries is built in exactly one place (candidateIn).
func (*Applier) HTTPClient ¶ added in v0.36.0
func (a *Applier) HTTPClient() *fetch.RetryableHTTPClient
HTTPClient returns the client the vendor step probes and downloads with; nil means the vendor step is off.
func (*Applier) MarkReenabled ¶
MarkReenabled tells the Applier that pkg's entry was re-enabled in packages.toml after the Applier loaded it, so its stale enabled = false no longer refuses the bump. hold = true still does. It needs an Applier built by NewApplier, which allocates the set.
func (*Applier) OverlayPath ¶
OverlayPath returns the overlay path.
func (*Applier) Pending ¶
func (a *Applier) Pending() *PendingList
Pending returns the pending list instance.
func (*Applier) RequirePin ¶
RequirePin returns the pin operator pkg's record declares for atom, and false when the record does not (any more) require it.
func (*Applier) SeedFromGentoo ¶
SeedFromGentoo copies the current ::gentoo package directory (srcPkgDir) into the overlay so a previously-removed (orphaned) package has a base ebuild to revive from. The overlay package dir may not exist yet — it is created with os.MkdirAll. Only the parts needed to bootstrap a bump are copied:
- the single ebuild matching gentooVersion (required; an absent source ebuild is a clear error),
- metadata.xml (optional; skipped silently when absent),
- the files/ subdirectory (optional; copied recursively, preserving the relative layout).
The Manifest is deliberately NOT copied: it is regenerated by the later bump's `pkgdev manifest` step against the overlay's own distfiles. Every failure is wrapped with %w.
func (*Applier) StagingRoot ¶
StagingRoot returns the directory this applier stages candidates under, empty when it was constructed without one (in which case no gate runs and the candidate is written straight into the published overlay).
It is exported because the staging root is where the EVIDENCE lives: the retained tree of every bump that was not promoted and the record beside it. A caller that has to tell an operator where to look — or that wants to prove that a promotion really came from a tree an earlier run left there — cannot do either from the option it passed in, because the applier is the thing that decides what to do with it.
func (*Applier) Validate ¶
func (a *Applier) Validate(ctx context.Context, pkg string, ceiling validate.Depth) validate.EbuildResult
Validate runs one pending update through the gates at its resolved depth and reports what each gate said, without publishing anything.
ceiling is the depth the operator confirmed for the whole run. A reviewer's escalation is held there rather than spent unasked: a plan that resolved entirely to `options` asked the operator about nothing, so a bump raised to `compile` afterwards would spend hours they never saw priced. DepthNone means "no ceiling", which is what a caller that did not ask anybody passes.
IT NEVER RETURNS AN ERROR, and that is deliberate. A check evaluates a BATCH: one package whose tree could not be staged, or whose pending entry went stale between the check and here, must not stop the other three hundred. Every such case comes back as a SKIPPED gate carrying the reason, which is what EbuildResult.WorstOutcome reads as "nothing was said about this bump" — never as a pass.
func (*Applier) VendorConfig ¶ added in v0.36.0
func (a *Applier) VendorConfig() config.VendorConfig
VendorConfig returns the autoupdate.vendor block this applier was built with.
type ApplierOption ¶
type ApplierOption func(*Applier)
ApplierOption is a functional option for configuring Applier
func WithAdoptedStagedTree ¶ added in v0.36.0
func WithAdoptedStagedTree(root string) ApplierOption
WithAdoptedStagedTree makes Apply adopt the staged tree at root, kept by an earlier failed apply and edited since, instead of staging a fresh candidate: the manifest step, the gates and promotion run on that tree's bytes exactly as they stand. No LLM agent runs on an adopted tree — the manifest fixer, the build fixer and the bump reviewer are left out of the run, whatever was wired — because its candidate is the operator's work, not an agent's input. The tree is never removed by the run; a failure keeps it and rewrites its repair bundle. An empty root leaves the applier as it was.
func WithApplierBuildConcurrency ¶ added in v0.36.0
func WithApplierBuildConcurrency(n int) ApplierOption
WithApplierBuildConcurrency sets how many applies may run their build gates at once (default DefaultApplyBuildConcurrency; values below 1 mean 1).
func WithApplierBuildFixer ¶
func WithApplierBuildFixer(fixer fixer.BuildFixer) ApplierOption
WithApplierBuildFixer wires an LLM build fixer into the applier. When the build gate fails for a reason attributable to the ebuild, the applier asks the fixer to repair the STAGED ebuild and then re-runs the same gate to decide. A nil fixer is ignored, preserving the fail-fast behaviour.
The nil discipline is WithApplierFixer's, deliberately and to the letter: a provider that could not be constructed leaves the applier exactly as it was rather than half-configured, so "no LLM was asked for" and "the LLM could not be built" produce the same, predictable run.
func WithApplierBumpReviewer ¶
func WithApplierBumpReviewer(reviewer fixer.BumpReviewer) ApplierOption
WithApplierBumpReviewer wires an LLM bump reviewer into the applier. It reads the difference between the two versions' upstream build declarations and may ask for MORE validation than policy chose; validate.Escalate applies the proposal and can only ever raise the depth.
A nil reviewer is ignored, exactly as WithApplierFixer ignores a nil fixer and for the same reason: a provider that could not be constructed leaves the applier as it was rather than half-configured, so "no LLM was asked for" and "the LLM could not be built" produce the same, predictable run.
func WithApplierClean ¶
func WithApplierClean(clean bool) ApplierOption
WithApplierClean enables removal of the previous version's ebuild after a successful apply, leaving only the newly created version (and pruning the Manifest's now-orphaned distfile entries). Mirrors the --clean / -c CLI flag.
func WithApplierDepth ¶
func WithApplierDepth(depth validate.Depth) ApplierOption
WithApplierDepth is `--depth`: the one input allowed to REPLACE the class, the package tier and configuration alike, in either direction.
It is the only lowering that is not reported as a skip, because the operator typing it is looking at one package and knows something the policy does not. A caller that omits this option leaves the pointer nil, which is how "the operator asked for nothing" stays distinguishable from "the operator asked for none" — the one confusion that switches validation off in silence.
func WithApplierDistdir ¶
func WithApplierDistdir(explicit, configured string) ApplierOption
WithApplierDistdir supplies the two configurable rungs of the distdir the Manifest step gives pkgdev: explicit is the --distdir flag, configured is autoupdate.distdir from the config file. Both empty — what a caller that omits this option gets — is the production default: the DISTDIR the host's own package manager reports.
func WithApplierDistfilesCache ¶
func WithApplierDistfilesCache(dir string) ApplierOption
WithApplierDistfilesCache supplies the read-only distfiles cache the Manifest step reuses already downloaded files from (--distfiles-cache / autoupdate.distfiles_cache). Empty disables the lookup, and empty is what a caller that omits this option gets.
func WithApplierFixer ¶
func WithApplierFixer(fixer fixer.ManifestFixer) ApplierOption
WithApplierFixer wires an LLM manifest fixer into the applier. When the manifest step fails, the applier asks the fixer to repair the ebuild and then re-runs the manifest to confirm. A nil fixer is ignored, preserving the fail-fast behaviour.
func WithApplierGentooPath ¶
func WithApplierGentooPath(dir string) ApplierOption
WithApplierGentooPath sets the ::gentoo tree consulted before an ebuild is renamed forward. An empty path disables the comparison, which is what tests that do not care about it get by default: a missing tree must not fail a bump.
func WithApplierHTTPClient ¶ added in v0.36.0
func WithApplierHTTPClient(c *fetch.RetryableHTTPClient) ApplierOption
WithApplierHTTPClient supplies the client the vendor step probes and downloads distfiles with. Without one the vendor step does nothing.
func WithApplierIsolationProbe ¶
func WithApplierIsolationProbe(fn func() (bool, string)) ApplierOption
WithApplierIsolationProbe replaces the network-namespace probe the compile gate measures with. It defaults to validate.ProbeIsolation; tests supply their own so both answers are reachable without privilege and without root.
func WithApplierLLMConcurrency ¶ added in v0.36.0
func WithApplierLLMConcurrency(n int) ApplierOption
WithApplierLLMConcurrency sets how many LLM agents may run at once (default DefaultApplyLLMConcurrency; values below 1 mean 1).
func WithApplierLogger ¶
func WithApplierLogger(l *slog.Logger) ApplierOption
WithApplierLogger sets the logger the applier, and every step it runs, reports its diagnostics to. Nil keeps the default, which discards them. Fixers and the bump reviewer are injected already built, and carry the logger their own options gave them.
func WithApplierOutcomeHint ¶ added in v0.36.0
func WithApplierOutcomeHint(hint func(*ApplyResult) string) ApplierOption
WithApplierOutcomeHint makes Apply ask hint for a next step to show beside the package's outcome in the progress reporter, e.g. the command that repairs a failure. The applier only relays the text; an empty answer shows nothing.
func WithApplierPackagesConfig ¶
func WithApplierPackagesConfig(cfg *registry.PackagesConfig) ApplierOption
WithApplierPackagesConfig supplies the per-package autoupdate config so the applier can honour a package's [meta] authenticated-fetch instructions before running the manifest step. A nil config (or one without a matching package) leaves the normal pkgdev-from-SRC_URI behaviour unchanged.
func WithApplierPendingDeleteFunc ¶
func WithApplierPendingDeleteFunc(fn func(pkg string) error) ApplierOption
WithApplierPendingDeleteFunc overrides the function Apply invokes to remove a package from pending.json after a successful apply. The default is a.pending.Delete. This option exists for tests that need to simulate a Delete failure; a nil fn is ignored.
func WithApplierPendingList ¶
func WithApplierPendingList(pending *PendingList) ApplierOption
WithApplierPendingList sets a custom pending list for the applier
func WithApplierQuietGateOutput ¶ added in v0.36.0
func WithApplierQuietGateOutput() ApplierOption
WithApplierQuietGateOutput keeps build-gate children's output out of the reporter's task tail. The output is still captured whole and returned.
func WithApplierReporter ¶
func WithApplierReporter(r tui.Reporter) ApplierOption
WithApplierReporter wires a progress reporter into the applier so Apply emits its lifecycle (TaskStart → TaskStage → TaskDone) to the TUI/plain sink. A nil reporter is normalized to tui.Noop(), preserving the silent, fully-buffered behaviour predating the TUI.
func WithApplierRequireIsolation ¶
func WithApplierRequireIsolation(require bool) ApplierOption
WithApplierRequireIsolation makes the compile gate REFUSE to run unisolated.
The setting lives in config.yaml as autoupdate.validate.require_isolation, not in the registry: a registry key is expensive to move once written, and in config.yaml an unknown key is a warning rather than a silently disabled record. It defaults to false, the behaviour before the key existed. This option remains the only way the value reaches the Applier, from the flag or from that key alike.
func WithApplierRequireProof ¶
func WithApplierRequireProof(require bool) ApplierOption
WithApplierRequireProof refuses to promote a bump whose build gates were SKIPPED, for a host where an unproved publish is not acceptable — a builder box, or a maintainer who would rather the sweep stopped than shipped something unbuilt.
It is an opt-in counterweight, not a contradiction: the default publishes such a bump with the depth it did not reach NAMED, because "this host lacks a build dependency" says nothing about the bump, and turning every one of those into a refusal would make the feature inert — the same reason isolation is not required by default.
func WithApplierRunAttached ¶
func WithApplierRunAttached(fn func(cmd *exec.Cmd) ([]byte, error)) ApplierOption
WithApplierRunAttached overrides how the compile test executes. The default (cmd.CombinedOutput) buffers the child's output, which is byte-identical to the pre-TUI behaviour but swallows the sudo/doas password prompt. The apply driver supplies a variant that hands the child the real terminal so the prompt is visible while teeing its raw output to the TTY and a capture buffer (the captured bytes still feed saveCompileLog on failure). A nil fn is normalized back to the CombinedOutput default.
func WithApplierRunCaptured ¶ added in v0.36.0
func WithApplierRunCaptured(fn func(cmd *exec.Cmd) error) ApplierOption
WithApplierRunCaptured overrides how a build-gate child that set its own stdin is run. Such a child never gets the terminal: its stdout and stderr are bound to the package's StreamCapture before fn is called, so fn only runs the command (the default is cmd.Run) and whatever it writes to cmd.Stdout reaches the task tail and the returned output. A nil fn is normalized back to cmd.Run.
func WithApplierSetVersionsFunc ¶
func WithApplierSetVersionsFunc(fn func(overlayPath string, pins map[string]string) error) ApplierOption
WithApplierSetVersionsFunc overrides the function Apply invokes to record the applied version in packages.toml after a successful apply. The default is SetPackageVersions. This option exists for tests that need to simulate a write failure or to observe the pin without a registry on disk; a nil fn is ignored. Production callers never supply it.
func WithApplierStagingRoot ¶
func WithApplierStagingRoot(dir string) ApplierOption
WithApplierStagingRoot points the applier at the directory its staged trees are built under, and by doing so turns the staged pipeline on: the candidate is materialised outside the published overlay, every gate reads it there, and the overlay is written exactly once, by promotion, after the gates have passed.
An empty (or blank) root is ignored, exactly as WithApplierFixer ignores a nil fixer and for the same reason: the caller that does not supply one keeps the behaviour it has always had — the candidate written straight into the overlay and rolled back on failure — rather than getting a half-configured pipeline. The production root is <configDir>/staging; Stage never picks one itself, because the path is where the retention rule is recorded.
func WithApplierValidatePolicy ¶
func WithApplierValidatePolicy(policy validate.DepthPolicy) ApplierOption
WithApplierValidatePolicy supplies the configured depth table and its per-package exceptions, which is how far each class of bump is validated.
The policy is expressed in the validate package's own types rather than in config's strings, so translating `autoupdate.validate` — including rejecting a mistyped depth by name through validate.ParseDepth — belongs to the caller. A zero policy is not "no validation": a class with no configured row falls through to the deepest rung, because failing to configure a depth is not evidence that a bump is small.
func WithApplierVendorConfig ¶ added in v0.36.0
func WithApplierVendorConfig(cfg config.VendorConfig) ApplierOption
WithApplierVendorConfig supplies the autoupdate.vendor block: the hosts whose Go vendor tarballs the applier owns, and how a generated one is uploaded.
func WithConfirmFunc ¶
func WithConfirmFunc(fn func(prompt string) bool) ApplierOption
WithConfirmFunc sets a custom confirmation function for the applier
func WithExecCommand ¶
func WithExecCommand(fn func(ctx context.Context, name string, arg ...string) *exec.Cmd) ApplierOption
WithExecCommand sets a custom context-aware exec.Command function for testing. The function mirrors exec.CommandContext so injected commands also observe context cancellation.
It replaces the PATH LOOKUP as well, and the two are deliberately one seam. The build gates ask whether `ebuild` and `emerge` exist before spawning them, so that a host with no Portage reports a named SKIP instead of an opaque failure. Leaving that question on the real PATH while the command itself is substituted would let the HOST decide whether the substituted child ever runs — `ebuild` exists on a Gentoo box and nowhere else — so the same code would take different branches on different machines for reasons that have nothing to do with the bump under test. A caller that has replaced how a child process is CREATED has replaced the process layer entire; this makes that true.
func WithLogsDir ¶
func WithLogsDir(dir string) ApplierOption
WithLogsDir sets a custom logs directory for the applier.
type ApplyResult ¶
type ApplyResult struct {
// Package is the full package name (category/package)
Package string
// OldVersion is the version before the update
OldVersion string
// NewVersion is the version after the update
NewVersion string
// Success indicates whether the apply operation succeeded
Success bool
// Error contains any error that occurred during application
Error error
// LogPath is the path to the compile log if compilation failed
LogPath string
// IsolationVerified reports whether the compile gate PROVED it could create
// a network namespace before building. It is false when the compile did not
// run at all.
//
// It exists because a compile-gate pass used to claim more than it had. The
// gate never checked: Portage reports network-sandbox in FEATURES, creating
// the namespace needs privilege an ordinary user does not have, and Portage
// says nothing when the unshare fails. So a green could mean "built with the
// network cut off" or "built with full network access", and nothing printed
// told them apart.
IsolationVerified bool
// IsolationReason says why isolation could not be verified, and is empty
// exactly when IsolationVerified is true. On a run skipped by
// --require-isolation it is the reason the compile never happened.
IsolationReason string
// CompileDistdir is the directory THIS RUN resolved for the privileged
// compile gate's archives — the apply's own fetch, then --distdir, then the
// configured one, whichever staticGateDistdir chose. It is recorded WHATEVER
// then happened to it, which is the point: a reader who only saw the
// enforced directory could not tell a run that resolved none from one whose
// privilege tool could not carry the one it had.
//
// It is empty when the compile did not run at all — including the run
// --require-isolation refused — and when this run resolved no directory of
// its own, which is the honest answer rather than an invented default:
// Portage then answers from its own configuration.
CompileDistdir string
// CompileDistdirEnforced reports whether that directory actually reached the
// ebuild child, and it is a separate fact because the privilege tool decides
// it. Measured on this host: sudo's env_reset
// discards an exported DISTDIR, so what carries the value is sudo's own
// argument form, `sudo DISTDIR=<dir> ebuild …`. `doas` has no such form —
// handed one it would try to execute a program named "DISTDIR=…" — so on a
// doas host the directory is resolved and NOT enforced.
//
// The pair therefore states three things a single field could not: nothing
// resolved (empty, false), resolved and enforced (dir, true), resolved but
// uncarriable (dir, false). The compile gate's reason is what says the third
// one out loud, rather than letting a PASS imply a hermeticity the run never
// had.
CompileDistdirEnforced bool
// CleanedOldVersion is the highest version whose ebuild --clean removed. It
// is the single-version view of CleanRemoved, kept because callers that
// print one "Removed: pkg-X.ebuild" line predate the sweep; empty when clean
// was off, removed nothing, or was blocked.
CleanedOldVersion string
// CleanKept maps each version --clean left in place to the registry entry
// key that claims it. An empty value means the version was kept by a
// rule rather than by an entry — the live -9999 rule or the last-non-live
// floor — since a registry key is never itself empty.
CleanKept map[string]string
// CleanRemoved lists the versions whose ebuilds --clean actually deleted,
// ascending. It is the executed plan, not the intended one: a sweep stopped
// by a removal failure reports the prefix it managed to delete.
CleanRemoved []string
// CleanWarning records a non-fatal failure of the --clean step (the update
// itself still succeeded). Empty on success.
CleanWarning string
// RegistryWarning records a non-fatal failure to write the applied version
// back into packages.toml. It is deliberately NOT CleanWarning:
// the two report different steps — the pin is written on every successful
// apply, the sweep only under --clean — and the CLI prints CleanWarning on a
// line labelled "Clean:", so reusing it would blame the wrong step for a
// registry failure. Empty when the pin was recorded.
RegistryWarning string
// MetadataCacheWarning records a non-fatal failure to regenerate the
// package's metadata/md5-cache entries after the bump. Empty on success,
// and when the overlay keeps no md5-cache.
MetadataCacheWarning string
// Obsolete indicates the pending entry no longer corresponds to anything to
// apply: the package was removed from the overlay, or the overlay is already
// at/beyond the target version. The entry is pruned from pending.json and
// this is NOT counted as a failure (Success stays false, Error stays nil).
Obsolete bool
// Held indicates the package carries hold = true in packages.toml and was
// therefore refused. Like Obsolete this is NOT a failure (Success false,
// Error nil), but unlike Obsolete the pending entry is KEPT: the update is
// real and still pending, it just may not be applied automatically. A held
// package reaches this point when its update was recorded before the hold
// was set (CheckPackage skips held packages, but pending.json outlives the
// check that wrote it).
//
// enabled = false is refused the same way and reported through the same
// field: both are a maintainer's "do not auto-bump", and HoldReason names
// which of the two keys said so.
Held bool
// Waiting lists the requirements a bump is waiting for, as atoms
// ("~dev-lang/dart-3.14.0"), or the instruction to re-run --check when the
// pending entry predates them. Like Held it is NOT a failure (Success false,
// Error nil) and the pending entry is kept: the bump applies once the
// required version is in the overlay or ::gentoo.
Waiting []string
// HoldReason is the packages.toml key that refused the package
// ("hold = true" or "enabled = false"). Empty unless Held is true.
HoldReason string
// ObsoleteReason explains, in user-facing terms, why the entry was deemed
// obsolete. Empty unless Obsolete is true.
ObsoleteReason string
// Fixed indicates a gate failed and was recovered by an LLM fixer: the ebuild
// was edited and BENTOO'S OWN re-run of that same gate then succeeded. Two
// gates can set it — the manifest step (`pkgdev manifest`) and the build
// gate — and in both cases the flag records the re-run's verdict, never the
// agent's self-report. A bump whose re-run
// still failed leaves this false, because a "fixed" flag on something still
// broken is worse than no flag. Only meaningful on the success path.
Fixed bool
// FixSummary is the fixer's one-line description of what it changed in the
// ebuild. Empty unless Fixed is true.
FixSummary string
// QASummary holds advisory `pkgcheck` output captured after an LLM fix, when
// pkgcheck is available. It never changes Success — the apply already passed
// the authoritative manifest re-run — but surfaces any QA findings the agent's
// edit may have introduced so a human can review before committing. Empty when
// pkgcheck is absent, reported nothing, or no fix was applied.
QASummary string
// StagedPath is the staged tree this bump was validated in: the
// single-package repository validate.Stage built, which by construction lives
// OUTSIDE the published overlay.
//
// It is set as soon as the tree exists and kept through every failure, because
// it is what makes a failure inspectable: the operator can read the exact
// ebuild the gates read and re-run a gate by hand, without repeating the work
// that produced it. Retention is expressed by the path itself — one tree per
// package and version — so there is no index to consult and nothing to unlock.
// applySummary appends it to the failure line; the CLI prints it as "Staged:".
//
// A COMPLETED promotion clears it again: once the bytes are in the overlay the
// tree says nothing new, and the field keeps its one meaning — "here is the
// evidence of what went wrong". Empty, therefore, after a successful apply,
// an apply with no staging root, and one that failed before staging.
StagedPath string
// StagedEbuild is the candidate ebuild inside StagedPath. It is set and
// cleared together with StagedPath.
StagedEbuild string
// FailedStage names the step a failed apply stopped in: manifest, llm-fix,
// re-check, gate:<name>, or one of the run's own stages (substitute,
// coverage, promotion, promote, retained). "apply" is the fallback for an
// exit that entered no stage. Empty unless the apply failed.
FailedStage string
// RefusedTools lists the tools an LLM fixer was refused during this apply,
// each label once, in the order they were first refused.
RefusedTools []string
// RepairBundle is the repair bundle written beside the kept staged tree of
// a failed apply. Empty when no bundle was written.
RepairBundle string
// DepthRequested is how far this bump was ASKED to be validated: the depth
// class, tier, configuration and the operator's flags resolved to, raised by
// a reviewer escalation where one applied.
//
// It is a string and not a validate.Depth for the reason
// validate.EbuildResult states: Depth is an int whose ORDERING is its
// contract, so it would reach a report as a number and force every reader to
// carry the ladder to interpret it. The spelling here is the one `--depth`
// accepts and Depth.String prints.
DepthRequested string
// DepthReached is how far validation ACTUALLY got — the deepest rung whose
// own gate reported PASS, never deeper than DepthRequested.
//
// The pair is the rule "an outcome names its own reach" applied to the
// ladder itself: a bump promoted with its build gates skipped for want of an
// installed dependency has DepthRequested "configure" and DepthReached
// short of it, so nobody can read the green as "it builds".
DepthReached string
// DepthReason names the input that decided the depth, and — when the two
// depths differ — why validation stopped short, naming the atoms or the host
// condition that stopped it. It is never empty on a staged apply.
DepthReason string
// ValidationSource says which of the two validation paths this bump took:
// "staged" when a retained tree that had already been proved was promoted as
// it stood, "this-run" when the gates ran here. It states per package which
// of the two happened, carried on the result so the reports and the summary
// line can both say it.
//
// Empty on an apply that ran with no staging root at all, because on that
// path no gate runs and neither answer would be true. The two constants are
// ValidationSourceStaged and ValidationSourceThisRun.
ValidationSource string
// Vendor is what the vendor step did before the staged manifest: when it
// generated a Go vendor tarball, the tarball's path, name, URI and digest;
// when its probe failed, ProbeFailed with the name and URI. The zero value
// means the step neither generated nor owned anything.
Vendor VendorOutcome
}
ApplyResult represents the result of applying an update.
type AuthDistfileRequest ¶
type AuthDistfileRequest struct {
// OverlayPath is the overlay holding .autoupdate/packages.toml and the
// package's ebuilds. Both are read; nothing in the overlay is written.
OverlayPath string
// Package is a "category/package" atom, or a full registry key carrying its
// ":slot"/"@label" suffix when the atom alone is ambiguous.
Package string
// Version is the version to substitute into the record's fetch_filename.
// Empty means "the version the overlay currently carries", resolved the same
// way the checker resolves it — highest ebuild, honouring the record's slot
// and series filters.
Version string
// DestDir is the directory the file is written into. It must already exist
// and be writable; this package does not create it, because the caller that
// knows whether the answer is a private distdir or the host's own DISTDIR is
// also the one that must decide whether creating it is appropriate.
DestDir string
}
AuthDistfileRequest names one authenticated download to perform.
type AuthDistfileResult ¶
type AuthDistfileResult struct {
// Package is the registry key that was used, which is the requested atom
// plus whatever suffix the matching record carried. Reported so a caller
// that passed an atom can name the record it actually acted on.
Package string
// Version is the version used, resolved or as requested.
Version string
// Path is the file written, inside DestDir.
Path string
// SerialEnv names the env var the serial came from, or "" when the record
// configures no serial. It is the NAME only — the value is never returned,
// logged or rendered anywhere.
SerialEnv string
}
AuthDistfileResult describes a completed download.
func FetchAuthDistfile ¶
func FetchAuthDistfile(ctx context.Context, log *slog.Logger, req AuthDistfileRequest) (AuthDistfileResult, error)
FetchAuthDistfile performs the authenticated download a package's [meta] block describes, and reports the file it wrote.
It is the exported form of what the sweep does before `pkgdev manifest`, and runs the same parser, the same guards and the same writer — deliberately, so that a user's fetch and a maintainer's sweep cannot disagree about what the vendor endpoint expects or about what counts as a valid response.
The error is the sentinel-wrapped kind: ErrPackageNotInRegistry, ErrAmbiguousPackageKey and ErrNoAuthFetch describe the request, while ErrAuthFetchSecretMissing and ErrAuthFetchFailed describe the download.
type BatchResult ¶
type BatchResult[T any] struct { // Items holds the successfully produced results of the batch operation. Items []T // Failures maps a package name (category/package) to the error that // occurred while processing it. Failures map[string]error }
BatchResult holds the outcome of a batch operation over multiple packages, such as checking or analyzing every configured package. It separates the successfully produced items from the per-package failures so callers can report partial success and derive a process exit code.
Concurrency: BatchResult itself provides no synchronization. When workers populate Failures from multiple goroutines, callers MUST ensure all worker goroutines have joined (for example, after wg.Wait()) before invoking any method on the BatchResult.
func (BatchResult[T]) ExitCode ¶
func (b BatchResult[T]) ExitCode() int
ExitCode returns the process exit code that represents the outcome of the batch operation:
- 0 when there are no failures.
- 2 when there are failures and no items were produced (total failure).
- 1 otherwise (partial failure: some items produced, some failed).
func (BatchResult[T]) FormatFailures ¶
func (b BatchResult[T]) FormatFailures(w io.Writer)
FormatFailures writes one line per recorded failure to w, in the form "ERROR <pkg>: <err>". Failures are emitted sorted lexically by package name so the output is deterministic regardless of map iteration order. Multi-line error strings are flattened: every newline within an error is rewritten to a newline followed by a two-space continuation indent, keeping each failure a single parseable logical record. Each record is terminated with a newline.
Concurrency: FormatFailures is NOT safe to call while other goroutines may still be writing to Failures. Callers MUST invoke it only after every worker goroutine has joined (for example, after wg.Wait()).
func (BatchResult[T]) HasFailures ¶
func (b BatchResult[T]) HasFailures() bool
HasFailures reports whether the batch operation recorded any per-package failures.
type CheckResult ¶
type CheckResult struct {
// Package is the full package name (category/package)
Package string
// CurrentVersion is the version currently in the overlay
CurrentVersion string
// UpstreamVersion is the version found upstream
UpstreamVersion string
// HasUpdate is true if upstream version is newer than current
HasUpdate bool
// NotComparable is true when the upstream value could not be ordered against
// the current version (e.g. a tag like "INKSCAPE_1_4_4" or an unparseable
// string). When set, HasUpdate is false and the package was NOT added to the
// pending list: the result is surfaced as a warning so a silent false
// "up to date" never masks a real update behind a bad parser config.
NotComparable bool
// Error contains any error that occurred during checking
Error error
// FromCache is true if the upstream version was retrieved from cache
FromCache bool
// Type classifies the package as "bin" or "source", resolved from the
// config's type field or auto-detected from the ebuild. Empty only when the
// current ebuild could not be read.
Type string
// Orphaned is true when the package no longer has any ebuild in the overlay
// (getCurrentVersion returned ErrNoEbuildFound). The checker auto-disables
// the entry (enabled = false) in packages.toml and surfaces the package as
// an informational result rather than a recurring hard failure. When set,
// all other fields except Package are zero-valued.
Orphaned bool
// Skipped names the packages.toml key ("hold = true" or "enabled = false")
// that kept CheckPackage from checking the package at all. When set, nothing
// was fetched, nothing was written to the cache or pending list, and every
// other field except Package is zero-valued.
Skipped string
// Requirements is the state of each `requires` entry captured for this
// update, in atom order; nil for a record without requires or a bump that
// was held.
Requirements []RequirementState
}
CheckResult represents the result of checking a single package for updates.
type Checker ¶
type Checker struct {
// contains filtered or unexported fields
}
Checker handles version checking operations for packages. It coordinates between configuration, cache, pending list, and upstream sources.
func NewChecker ¶
func NewChecker(overlayPath string, opts ...CheckerOption) (*Checker, error)
NewChecker creates a new checker instance for the given overlay. It loads the packages configuration and initializes cache and pending list.
func (*Checker) CheckAll ¶
func (c *Checker) CheckAll(ctx context.Context, force bool) BatchResult[CheckResult]
CheckAll checks all packages in the configuration for updates. If force is true, the cache is bypassed for all packages.
It returns a BatchResult: checked packages land in Items, a per-package failure in Failures keyed by package name.
Packages run concurrently, bounded by WithConcurrency. The semaphore is taken with a context-cancellable select: once ctx is cancelled the remaining packages are not dispatched but recorded with the context error, so a SIGINT stops the batch promptly. Every worker recovers a CheckPackage panic as a failure, and all writes to the shared maps are mutex-guarded. Items are sorted by package name, and the result is returned only after every worker has joined (wg.Wait), so callers may call ExitCode and FormatFailures directly.
func (*Checker) CheckPackage ¶
CheckPackage checks a single package for updates. If force is true, the cache is bypassed and upstream is queried directly. ctx bounds every upstream fetch, rate-limit wait and LLM call this check makes; the Checker holds no context of its own, so one Checker can serve calls with different lifetimes.
func (*Checker) Config ¶
func (c *Checker) Config() *registry.PackagesConfig
Config returns the packages configuration.
func (*Checker) DisableOrphans ¶
DisableOrphans marks each package as disabled (enabled = false) and, where the record states no origin yet, records the checker as the origin of that disable (disabled_by = "auto") — an entry that already names a human origin keeps it, or this function would erase the intent it exists to respect — both in the overlay's packages.toml and in the in-memory config, so a package whose ebuild was removed from the overlay stops being processed on subsequent runs — and so the reconciliation in CheckAll can tell this disable from one a human wrote and is free to clear it when the ebuild comes back. The file edit is a single atomic, comment-preserving write for the whole batch. A nil or empty slice is a no-op. The in-memory config is only updated after the file write succeeds, so a failed write leaves both views consistent.
Both keys are mirrored in memory, not just enabled: within a single run the reconciliation READS the in-memory origin, so writing the pair to the file and only half of it to memory would make the checker's own disable look like a legacy human one on the very next scan.
func (*Checker) FindRevivableOrphans ¶
func (c *Checker) FindRevivableOrphans(ctx context.Context, prov provider.Provider) ([]ReviveCandidate, error)
FindRevivableOrphans scans the disabled entries in the config and reports those an autoupdate could revive: the entry's upstream version is strictly newer than the highest version ::gentoo still ships. CheckAll skips disabled entries forever, so without this report a package removed from the overlay would never surface an upstream bump ::gentoo has not caught up to.
A candidate must be BOTH disabled AND absent from the overlay (a true orphan): a disabled entry whose ebuild is still present is not revivable, as a ::gentoo base would seed an older version over the newer one shipped.
An entry whose ":slot" or `series` filter matches NOTHING is skipped just as silently: its directory holds another line, so it is not an orphan, and a disabled line-filtered entry matching nothing is its expected state. A real config mistake is caught on the enabled path (CheckAll, Reconcile's NoEbuild).
Every network call is best-effort: a failed upstream fetch or a package ::gentoo does not carry is skipped; other provider errors become soft notes in the returned error without dropping the candidates gathered. The result is sorted by package name.
func (*Checker) OverlayPath ¶
OverlayPath returns the overlay path.
func (*Checker) Pending ¶
func (c *Checker) Pending() *PendingList
Pending returns the pending list instance.
func (*Checker) ReviveDisabled ¶
ReviveDisabled re-enables each named package in the overlay's packages.toml and in the in-memory config. It is the inverse of DisableOrphans: once an auto-disabled package's ebuild is back in the overlay — the source of truth for whether a package exists — it is reconciled to enabled. The file edit is a comment-preserving DELETION of both keys of the disable, `enabled = false` and its `disabled_by` origin: enabled is spelled by the key's absence, so writing `enabled = true` would leave a redundant-enabled finding for --lint --fix, and a leftover origin would claim a state the record is no longer in. A nil or empty slice is a no-op. The in-memory config changes only after the file write succeeds, so a failed write leaves both views consistent — including the origin, which the reconciliation reads later in the same run.
Callers must exclude held packages (hold = true): a hold is an explicit maintainer decision that the overlay reconciliation must never flip. Callers must likewise pass only packages reconcilesAutomatically accepts; this function performs the revive it is asked for and does not second-guess it.
type CheckerOption ¶
CheckerOption is a functional option for configuring Checker
func WithCache ¶
func WithCache(cache *fetch.Cache) CheckerOption
WithCache sets a custom cache for the checker
func WithCacheTTL ¶
func WithCacheTTL(d time.Duration) CheckerOption
WithCacheTTL sets the TTL applied to the default Cache constructed by NewChecker when no Cache is injected via WithCache. It enables `autoupdate.cache_ttl` from ~/.config/bentoo/config.yaml to reach Cache.TTL . A non-positive duration is rejected at construction time, mirroring WithOpTimeout's validation; the CLI guards the value upstream via AutoupdateConfig.GetCacheTTL, so this is defence-in-depth for direct callers.
func WithConcurrency ¶
func WithConcurrency(n int) CheckerOption
WithConcurrency sets the maximum number of packages CheckAll processes in parallel. n must be in the inclusive range [1, maxConcurrency]; a value outside that range is rejected. When this option is not supplied the Checker uses DefaultConcurrency.
func WithConfigDir ¶
func WithConfigDir(dir string) CheckerOption
WithConfigDir sets the configuration directory for cache and pending files
func WithFetchCache ¶
func WithFetchCache(enabled bool) CheckerOption
WithFetchCache turns per-run deduplication of upstream response bodies on or off. It is ON by default — NewChecker builds a cache in its struct literal — so the only reason to pass this option at all is to turn it OFF.
DISABLED IS THE ABSENCE OF A CACHE, not a flag consulted on the fetch path: false sets the field to nil, and fetchContent calls fetchContentUncached directly. That makes the off switch a trustworthy bisection tool — the disabled path is not a second implementation to audit, it is the uncached code byte for byte, with no join, no sharing and no retention.
true INSTALLS a cache only when none is present, so the option is idempotent: applied after the default construction it keeps the cache already there with its accumulated bodies and counters, instead of discarding them and paying for every earlier read a second time.
It cannot fail: a bool has no invalid value. The error in the return type is the CheckerOption signature, not a possibility.
func WithGentooPath ¶
func WithGentooPath(path string) CheckerOption
WithGentooPath sets the ::gentoo tree consulted, after the overlay, when the check report decides whether a required version is present.
func WithGitHubTokenErrorSink ¶ added in v0.35.0
func WithGitHubTokenErrorSink(sink func(error)) CheckerOption
WithGitHubTokenErrorSink hands the error of the checker's GitHub token resolution to sink instead of logging it as a warning, so a caller that builds several checkers, or reads the same secrets file elsewhere, can report the failure once. The checker still continues with unauthenticated access. A nil sink keeps the default: the checker logs its own warning.
func WithHTTPClient ¶
func WithHTTPClient(client *fetch.RetryableHTTPClient) CheckerOption
WithHTTPClient sets a custom HTTP client for the checker
func WithHTTPRequestTimeout ¶
func WithHTTPRequestTimeout(d time.Duration) CheckerOption
WithHTTPRequestTimeout sets the per-request HTTP timeout — the cap on a single outbound attempt. NewChecker applies it to the HTTP client and, unless WithOpTimeout overrode the budget explicitly, derives the per-operation budget from it so every retry attempt fits within the deadline (see deriveOpTimeout). A non-positive duration is a no-op (the client keeps its default), so callers that wire this option unconditionally can pass an unresolved zero safely.
func WithLLMClient ¶
func WithLLMClient(llm llm.LLMProvider) CheckerOption
WithLLMClient sets the LLM provider used by --check's version-extraction fallback. It accepts any LLMProvider (AD2), so a non-claude provider — which the pre-refactor *LLMClient signature could not express — is now valid; the legacy *LLMClient still satisfies the interface and remains accepted.
A nil provider is ignored (the field is left untouched, i.e. nil), mirroring WithRateLimiter's nil rejection. This is defence-in-depth: the CLI must wire this option only with a successfully constructed, non-nil provider, because a typed-nil interface (a nil concrete pointer boxed by a failed constructor) would pass `!= nil` and make fetchUpstreamVersion call ExtractVersion on a nil receiver. Refusing nil here keeps llmClient an untyped nil when no usable provider exists.
func WithLLMProviderConfigured ¶
func WithLLMProviderConfigured(configured bool) CheckerOption
WithLLMProviderConfigured records whether the CLI attempted to configure an LLM provider for this run (true when autoupdate.llm.provider was non-empty), independent of whether the provider was successfully built and wired via WithLLMClient. It exists to gate the "unused llm_prompt" Warn so that warning fires only when NO provider was configured; when a provider was configured but failed to construct, runCheck logs its own failure Warn and this flag suppresses the duplicate construction Warn. Defaults false, so callers that omit it preserve the pre-refactor warn behaviour.
func WithLogger ¶
func WithLogger(l *slog.Logger) CheckerOption
WithLogger sets the logger the checker reports its diagnostics to, and hands it to the HTTP client the checker uses. Nil keeps the default, which discards them (and leaves an injected client's own logger alone).
func WithOpTimeout ¶
func WithOpTimeout(d time.Duration) CheckerOption
WithOpTimeout sets the per-operation timeout used to derive a child context for each outbound fetch. A non-positive duration is rejected. Setting it marks the budget as explicit, so NewChecker will not overwrite it with the value derived from WithHTTPRequestTimeout.
func WithPackagesConfig ¶
func WithPackagesConfig(config *registry.PackagesConfig) CheckerOption
WithPackagesConfig sets a custom packages configuration
func WithPendingList ¶
func WithPendingList(pending *PendingList) CheckerOption
WithPendingList sets a custom pending list for the checker
func WithPerHostConcurrency ¶
func WithPerHostConcurrency(n int) CheckerOption
WithPerHostConcurrency caps the requests in flight to one host at n; n < 1 removes the cap.
func WithProgressCallback ¶
func WithProgressCallback(cb ProgressCallback) CheckerOption
WithProgressCallback sets a callback invoked once per package as CheckAll completes that package's work. A nil callback disables progress reporting. See ProgressCallback for the concurrency contract.
func WithRateLimiter ¶
func WithRateLimiter(limiter httpRateLimiter) CheckerOption
WithRateLimiter sets a custom HTTP rate limiter for the checker. The limiter is consulted (per host) before every outbound fetch. A nil limiter is rejected; when this option is not supplied NewChecker installs a default limiter, so the Checker's rateLimiter is never nil after construction.
func WithTypeFilter ¶
func WithTypeFilter(t string) CheckerOption
WithTypeFilter restricts CheckAll to packages of the given type, "bin" or "source". An empty string (the default) checks every package. Type is resolved from each package's configured type field, falling back to ebuild auto-detection. An unrecognized value is rejected.
type ChildRunError ¶ added in v0.36.0
type ChildRunError struct {
// Label names the run, e.g. "first manifest run".
Label string
// Output is the last tui.MaxCapturedBytes the run printed, stdout and
// stderr interleaved, with every resolved secret masked. When the run
// printed more, a first line states how many earlier bytes were dropped.
Output string
// LogPath is the file holding everything the run printed, from its first
// byte, with every resolved secret masked; empty when it could not be
// written or no logs directory was configured.
LogPath string
// Err is how the run ended (an *exec.ExitError, a timeout); Unwrap returns
// it, so errors.Is/errors.As still see the process failure.
Err error
}
ChildRunError is one failed child process run: what the run was, the tail of what it printed, and where the whole of it was kept.
Error() is a single line — the label, the run error and the first line of Output that reads as an error — so an error holding several runs stays short wherever it is printed whole. The output itself travels in Output, its last tui.MaxCapturedBytes, for a renderer that bounds it, and in the file at LogPath, which keeps all of it.
func ChildRuns ¶ added in v0.36.0
func ChildRuns(err error) []*ChildRunError
ChildRuns returns every distinct *ChildRunError in err's chain, following both single and multi-error Unwrap, in the order they are first reached. A run reachable along two branches is returned once.
func (*ChildRunError) Error ¶ added in v0.36.0
func (e *ChildRunError) Error() string
func (*ChildRunError) Unwrap ¶ added in v0.36.0
func (e *ChildRunError) Unwrap() error
type DataSource ¶
type DataSource struct {
// URL is the endpoint to query for version information
URL string
// Type identifies the source type: "github", "pypi", "npm", "crates", "homepage", "provided"
Type string
// Priority determines the order of sources (lower is higher priority)
Priority int
// ContentType is the expected content type (e.g., "application/json", "text/html")
ContentType string
}
DataSource represents a candidate data source for version checking. It contains information about where to fetch version information and how to prioritize different sources.
func DiscoverDataSources ¶
func DiscoverDataSources(meta *ebuilds.EbuildMetadata, providedURL string) []DataSource
DiscoverDataSources finds candidate URLs for version checking. It analyzes ebuild metadata and returns a prioritized list of data sources. If providedURL is non-empty, it is included as the highest priority source.
type Divergence ¶
type Divergence struct {
// Key identifies what the divergence is about: the registry key
// ("net-libs/webkit-gtk:4.1") for StalePin and NoEbuild, the bare atom of
// the directory for UnclaimedEbuild — see that constant for why. Either way
// it is identity only: never build a path from it, always split it first.
Key string
// Kind is which of the three classes this is.
Kind DivergenceKind
// Pin is the version the registry declares (PackageConfig.Version), empty
// when the entry has no pin — which is every entry today.
Pin string
// Disk is the version the overlay actually holds: the ebuild the entry
// resolves to (StalePin), or the stray ebuild itself (UnclaimedEbuild).
// Empty for NoEbuild, where the point is that there is none.
Disk string
}
Divergence is one disagreement between what the registry claims and what the overlay holds.
The invariants a consumer may rely on, per kind:
StalePin Key = registry key · Disk != "" · Disk != Pin → writable UnclaimedEbuild Key = "category/package" atom · Pin = "" → report only NoEbuild Key = registry key · Disk = "" → report only
func Reconcile ¶
func Reconcile(log *slog.Logger, overlayPath string, cfgs map[string]registry.PackageConfig) []Divergence
Reconcile compares every enabled entry's pin against the ebuild it resolves to on disk, for the whole registry, and returns the divergences in three classes: StalePin, UnclaimedEbuild and NoEbuild.
It only ever reads. The registry is a published artifact — the overlay auto-commits and pushes, so a wrong pin is a released one — so the caller shows the whole set, takes ONE confirmation for all of it, leaves packages.toml byte-identical on "no" and refuses to write from a non-TTY without --yes. Nothing here should ever start writing packages.toml.
Only enabled entries can be the SUBJECT of a divergence, but the unclaimed scan counts every entry of a directory: a switched-off entry still holds its file. Resolution goes through selectCurrentEbuild; its not-found sentinels land in NoEbuild, and any other error skips that entry with a warning — a fabricated divergence would become a fabricated, published pin. The result is sorted, so two runs differ only when the overlay did. It costs about 2-3 readdir per enabled entry; CheckResult could not replace the scan, as it carries no directory listing. Skips are warned about to log; nil discards them.
type DivergenceKind ¶
type DivergenceKind int
DivergenceKind classifies one disagreement between the registry and the overlay. The three kinds are NOT interchangeable: only StalePin carries a version the reconciliation may write, so a consumer that builds a write batch MUST switch on the kind rather than map every divergence to Key -> Disk.
const ( // StalePin — the entry resolves to an ebuild whose version is not the one // it declares. Disk is the version on disk (never empty) and Pin is what the // registry says today. // // Pin is empty for the whole first reconciliation: all 409 records are // pinless right now, so an enabled entry that resolves to an ebuild diverges // from its (absent) pin. That is deliberate and load-bearing — it is exactly // the ~317-entry first bulk fill, and the only class the write batch is // built from. A caller wording the prompt can still tell the two apart // (Pin == "" reads "pin 317 entries for the first time", Pin != "" reads // "correct N stale pins"); the reconciliation itself does not, because the // repair is identical: write Disk. StalePin DivergenceKind = iota // UnclaimedEbuild — a non-live ebuild that no entry of its directory pins or // resolves to. // // It is a property of a DIRECTORY, not of an entry: several entries share // one directory (":slot" and "@label" siblings), and reporting the same // stray file once per sibling would inflate the prompt's count of what is // about to happen. So it is emitted ONCE per file, and Key holds the bare // "category/package" atom rather than a registry key. Pin is always empty — // nothing pins it, that IS the finding — and nothing about it is writable: // the repair is a sweep or a new registry entry, both decided by a human. UnclaimedEbuild // NoEbuild — the entry is enabled but its directory holds no ebuild it can // select: the package was removed, or its ":slot"/`series` filter matches // nothing there. // // Disk is always empty, so there is nothing to write (the registry never // holds a version that is not on disk) and reporting it is the whole of the // action. The existing orphan reconciliation, not this one, is what acts on // a removed package. NoEbuild )
func (DivergenceKind) String ¶
func (k DivergenceKind) String() string
String renders a kind as the stable identifier a report and a filter can both use, in the kebab-case the lint rules already use.
type FallbackSuggestion ¶
type FallbackSuggestion struct {
// ParserType is the type of fallback parser
ParserType string
// Reliability is the reliability score (lower is better)
Reliability ParserReliability
// Reason explains why this fallback is suggested
Reason string
}
FallbackSuggestion represents a suggested fallback parser configuration.
func GetBestFallback ¶
func GetBestFallback(primaryParser string) *FallbackSuggestion
GetBestFallback returns the single best fallback for a given primary parser. It returns the most reliable fallback that is different from the primary parser.
func OrderFallbacksByReliability ¶
func OrderFallbacksByReliability(fallbacks []FallbackSuggestion) []FallbackSuggestion
OrderFallbacksByReliability sorts fallback suggestions by reliability. This ensures fallbacks are tried in order of reliability (JSON > HTML > regex > LLM).
func SuggestFallbacks ¶
func SuggestFallbacks(primaryParser string) []FallbackSuggestion
SuggestFallbacks suggests appropriate fallback parsers based on the primary parser type. It returns fallbacks ordered by reliability (JSON > HTML > regex > LLM). The primary parser type is excluded from suggestions.
type ParserReliability ¶
type ParserReliability int
ParserReliability defines the reliability order of parsers. Lower values indicate higher reliability. Order: JSON (1) > HTML (2) > regex (3) > LLM (4)
const ( // ReliabilityJSON is the highest reliability (structured data) ReliabilityJSON ParserReliability = 1 // ReliabilityHTML is second highest (semi-structured) ReliabilityHTML ParserReliability = 2 // ReliabilityRegex is third (pattern matching) ReliabilityRegex ParserReliability = 3 // ReliabilityLLM is lowest (AI-based extraction) ReliabilityLLM ParserReliability = 4 )
func GetParserReliability ¶
func GetParserReliability(parserType string) ParserReliability
GetParserReliability returns the reliability score for a parser type. Lower scores indicate higher reliability.
type PendingList ¶
type PendingList struct {
// Updates holds all pending updates, keyed by package name
Updates map[string]PendingUpdate `json:"updates"`
// contains filtered or unexported fields
}
PendingList manages the list of pending updates. It persists updates to disk and supports concurrent access.
func NewPendingList ¶
func NewPendingList(configDir string, opts ...PendingListOption) (*PendingList, error)
NewPendingList creates or loads a pending list from disk. If the pending file exists, it loads existing entries. If the pending file doesn't exist or is corrupted, it creates a new empty list. The configDir should be the bentoo config directory (e.g., ~/.config/bentoo/autoupdate).
func (*PendingList) Add ¶
func (p *PendingList) Add(update PendingUpdate) error
Add adds or updates a pending update. If the package already exists, it updates the entry. It automatically saves the pending list to disk after adding.
func (*PendingList) Clear ¶
func (p *PendingList) Clear() error
Clear removes all entries from the pending list. It automatically saves the pending list to disk after clearing.
func (*PendingList) Delete ¶
func (p *PendingList) Delete(pkg string) error
Delete removes a package from the pending list. It automatically saves the pending list to disk after deletion.
func (*PendingList) Get ¶
func (p *PendingList) Get(pkg string) (*PendingUpdate, bool)
Get retrieves a pending update by package name. Returns the update and true if found, zero value and false otherwise.
func (*PendingList) Has ¶
func (p *PendingList) Has(pkg string) bool
Has checks if a package exists in the pending list.
func (*PendingList) Len ¶
func (p *PendingList) Len() int
Len returns the number of entries in the pending list.
func (*PendingList) List ¶
func (p *PendingList) List() []PendingUpdate
List returns all pending updates as a slice. The returned slice contains copies of the updates.
func (*PendingList) ListByStatus ¶
func (p *PendingList) ListByStatus(status UpdateStatus) []PendingUpdate
ListByStatus returns all pending updates with the specified status.
func (*PendingList) Save ¶
func (p *PendingList) Save() error
Save persists the pending list to disk. This is thread-safe and can be called concurrently.
func (*PendingList) SetStatus ¶
func (p *PendingList) SetStatus(pkg string, status UpdateStatus, errMsg string) error
SetStatus updates the status of a pending update. The errMsg parameter is used to set the Error field when status is StatusFailed. Returns ErrPackageNotInPending if the package is not found.
type PendingListOption ¶
type PendingListOption func(*PendingList)
PendingListOption is a functional option for configuring PendingList
func WithPendingNowFunc ¶
func WithPendingNowFunc(fn func() time.Time) PendingListOption
WithPendingNowFunc sets a custom time function for testing
type PendingUpdate ¶
type PendingUpdate struct {
// Package is the full package name (category/package)
Package string `json:"package"`
// CurrentVersion is the version currently in the overlay
CurrentVersion string `json:"current_version"`
// NewVersion is the upstream version detected
NewVersion string `json:"new_version"`
// CommitHash is the upstream commit SHA for snapshot packages tracked via
// track="commit". When non-empty, Apply substitutes the old commit variable
// (EGIT_COMMIT / GIT_COMMIT / COMMIT) in the copied ebuild with this hash.
CommitHash string `json:"commit_hash,omitempty"`
// AuxValue is the free-text value captured via the package's aux_pattern
// (e.g. "esr-bb24"). When non-empty, Apply substitutes the aux_var assignment
// in the copied ebuild with this value.
AuxValue string `json:"aux_value,omitempty"`
// Requires maps each package the record requires ("dev-lang/dart") to the
// version captured for it from the same upstream release ("3.14.0"). Empty
// for a record without `requires`, and absent from entries written before
// the field existed — the applier treats that absence on a record that does
// declare requires as "not captured", never as "no requirement".
Requires map[string]string `json:"requires,omitempty"`
// Status is the current status of this update
Status UpdateStatus `json:"status"`
// DetectedAt is when this update was first detected
DetectedAt time.Time `json:"detected_at"`
// Error contains error message if status is failed
Error string `json:"error,omitempty"`
}
PendingUpdate represents a detected update awaiting application.
type ProgressCallback ¶
type ProgressCallback func(done, total uint64)
ProgressCallback reports batch progress as a check proceeds. It is invoked once per package as that package's work completes, with done being the cumulative count of packages finished so far and total the number of packages in the batch.
Because CheckAll runs packages concurrently, the callback may fire from multiple goroutines and the per-invocation order is not deterministic. However done is sourced from an atomic counter, so the value observed by any single invocation is monotone non-decreasing: each callback sees a strictly larger done than every callback that ran before it.
type RegistryFixAttempt ¶
type RegistryFixAttempt struct {
// Package is the package the attempt was for.
Package string
// Status is the attempt's outcome.
Status RegistryFixStatus
// Stage is the step a Skipped or Reverted attempt stopped at; zero for
// Passed and StillFailing.
Stage RegistryFixStage
// Result is the agent's result (Model, Summary, DeniedTools); nil when the
// fixer never ran.
Result *fixer.RegistryFixResult
// Recheck is the fresh re-check's result; nil unless a re-check ran.
Recheck *CheckResult
// RecheckErr is the still-failing error shown to the operator: the
// re-check's own Error when it is set, the CheckPackage error otherwise.
RecheckErr error
// Err is the RAW cause of a Skipped or Reverted attempt, with no extra
// wrapping, so the caller's printed %v text is unchanged; nil otherwise.
Err error
// RestoreErr is set when the automatic restore of a Reverted attempt failed.
RestoreErr error
// contains filtered or unexported fields
}
RegistryFixAttempt is the data record of one snapshot-guarded registry fix. It carries no terminal output: the caller decides what to print from Status, Stage and the errors.
func AttemptRegistryFix ¶
func AttemptRegistryFix(ctx context.Context, overlayPath, pkg string, fetchErr error, registryFixer fixer.RegistryFixer, newChecker func() (*Checker, error)) *RegistryFixAttempt
AttemptRegistryFix runs one snapshot-guarded fix for pkg: it captures the raw packages.toml bytes and mode, hands the package's current entry to registryFixer, and re-checks the package through a FRESH Checker from newChecker so the edited config is reloaded from disk. Success is decided by that re-check, never by the agent's summary.
It never returns with packages.toml in a half-edited state except when Status is RegistryFixStillFailing, which the caller settles with Revert or by keeping the edit. A fixer or checker-construction error restores the snapshot before returning; a failure of that restore is reported in RestoreErr, not returned early. Nothing here prints.
func (*RegistryFixAttempt) Revert ¶
func (a *RegistryFixAttempt) Revert() error
Revert restores the pre-attempt packages.toml bytes and permission bits atomically, through the shared fileutil.WriteFileAtomic — the helper every other registry writer uses — so the restored registry carries exactly the captured mode whatever the umask and whatever stale temporary file sits beside it. A Skipped attempt (the zero Status) never edited the file and may hold no snapshot, so reverting it is a no-op rather than a write of empty bytes.
type RegistryFixStage ¶
type RegistryFixStage int
RegistryFixStage names the step a Skipped or Reverted attempt stopped at, so the caller prints the same line it prints today for each of the four. It is zero for Passed and StillFailing.
const ( // RegistryFixStageSnapshot: the read or stat of packages.toml failed → Skipped. RegistryFixStageSnapshot RegistryFixStage = iota + 1 // RegistryFixStageLoad: LoadPackagesConfig failed → Skipped. RegistryFixStageLoad // RegistryFixStageFixer: FixRegistry returned an error → Reverted. RegistryFixStageFixer // RegistryFixStageChecker: the re-check checker could not be built → Reverted. RegistryFixStageChecker )
type RegistryFixStatus ¶
type RegistryFixStatus int
RegistryFixStatus is the outcome of one AttemptRegistryFix.
const ( // RegistryFixSkipped means no edit was made: the snapshot or the load of // packages.toml failed, so the fixer never ran and nothing was written. RegistryFixSkipped RegistryFixStatus = iota // RegistryFixReverted means the fixer or the re-check setup failed and the // snapshot has already been restored (or RestoreErr says why it could not be). RegistryFixReverted // RegistryFixPassed means the fresh re-check extracted a version; the edit // is kept. RegistryFixPassed // RegistryFixStillFailing means the re-check still fails; the edit is on // disk and the caller settles it by keeping it or calling Revert. RegistryFixStillFailing )
type RequirementState ¶
type RequirementState struct {
// Package is the required "category/package".
Package string
// Version is the version captured for it.
Version string
// State is "present" (the overlay or ::gentoo satisfies the pin), "pending"
// (a pending entry of that package will) or "missing" (neither).
State string
}
RequirementState is whether the version a bump requires is available yet.
type ReviveApplier ¶
type ReviveApplier interface {
SeedFromGentoo(pkg, srcDir, version string) error
// MarkReenabled tells the applier the entry was re-enabled after it loaded
// packages.toml, so its stale enabled = false does not refuse the bump.
MarkReenabled(pkg string)
Apply(ctx context.Context, pkg string, compile bool) (*ApplyResult, error)
}
ReviveApplier is the part of *Applier the pipeline drives. It is an interface so a unit test can make Apply report an obsolete bump.
type ReviveCandidate ¶
type ReviveCandidate struct {
// Package is the full package name (category/package).
Package string
// GentooVersion is the highest version found in ::gentoo for the package.
GentooVersion string
// UpstreamVersion is the version reported by the package's upstream source.
UpstreamVersion string
}
ReviveCandidate describes a disabled (orphaned) packages.toml entry whose upstream release is strictly newer than the highest version ::gentoo still carries. It is a passive report: FindRevivableOrphans never mutates the config or the overlay, it only flags entries a later revive step could resurrect.
type ReviveOutcome ¶
type ReviveOutcome struct {
Package string
Status ReviveStatus
Detail string
}
ReviveOutcome is the result of reviving one package. Detail is the human-facing note: "<gentoo> → <upstream>" when revived, the skip reason, or the failing step's error.
type ReviveStatus ¶
type ReviveStatus string
ReviveStatus is the result class of reviving one package.
const ( ReviveRevived ReviveStatus = "revived" ReviveSkipped ReviveStatus = "skipped" ReviveWaiting ReviveStatus = "waiting" ReviveFailed ReviveStatus = "failed" )
The revive results the summary keys on. ReviveWaiting is a bump whose required versions are not available yet: the entry is re-enabled and the bump stays pending, so it is not a failure.
type Reviver ¶
type Reviver struct {
// contains filtered or unexported fields
}
Reviver resurrects orphaned packages one at a time: it seeds the highest ::gentoo ebuild into the overlay, re-enables the packages.toml entry, re-checks upstream with the cache bypassed and applies the bump. One Reviver serves every target of a run.
func NewReviver ¶
func NewReviver(overlayPath string, applier ReviveApplier, prov provider.Provider, newChecker func() (*Checker, error), opts ...ReviverOption) (*Reviver, error)
NewReviver returns ErrNoLocalPackageDir (wrapped) when prov is not a provider.PackageDirProvider. newChecker must build a Checker that shares the applier's PendingList: the entry CheckPackage writes is read back by Apply in the same process, from the same in-memory map.
func (*Reviver) Revive ¶
func (r *Reviver) Revive(ctx context.Context, pkg string) ReviveOutcome
Revive performs the full revive of one package and returns its outcome. It never returns an error: every failure is an outcome, so the caller can continue with the remaining targets.
Steps, in order: locate the ::gentoo package dir, pick the highest ::gentoo version, seed it into the overlay, re-enable the entry in packages.toml BEFORE checking (so the checker does not skip it), CheckPackage(force=true) on a fresh Checker to populate pending with the upstream version, then Apply.
type ReviverOption ¶
type ReviverOption func(*Reviver)
ReviverOption configures a Reviver.
func WithReviveCompile ¶
func WithReviveCompile(compile bool) ReviverOption
WithReviveCompile passes the compile request to the apply of every revived bump (--compile).
type ScriptParser ¶
type ScriptParser struct {
URL string
Script string
Headers map[string]string
// contains filtered or unexported fields
}
ScriptParser extracts a version by evaluating JS against a live page. Unlike the Parser implementations it navigates itself (it needs the rendered DOM), so it is driven by Checker.parseLive rather than the NewParserFromConfig path and the Parser interface (there is no pre-fetched []byte to hand it).
func (*ScriptParser) ParseLive ¶
func (p *ScriptParser) ParseLive(ctx context.Context) (string, error)
ParseLive renders URL and evaluates Script, returning the trimmed result. The script is responsible for producing a Gentoo-formatted version string (it may do its own transform/selection in JS) — transform/select from the TOML do not apply on this path.
type SpaceFunc ¶
SpaceFunc reports how many bytes are still available to the INVOKING user on the filesystem backing dir. It is the seam that makes the full-filesystem branch testable without filling a real disk.
Contract:
- the result is a byte count, not blocks, and it is the space available to an unprivileged user — the reserved blocks root can still use are not ours and must not be counted;
- a non-nil error means the question could not be answered. It does NOT mean "no space". ClassifyManifestFailure treats an unanswerable check as no evidence and moves on, because a classifier that manufactured an "environment" verdict out of a failed statfs would gate away a repair on the strength of knowing nothing.
type SweepBatch ¶
type SweepBatch struct {
// Dirs holds one entry per candidate directory, sorted by Atom.
Dirs []SweepDirPlan
// PlanErrors holds the directories that could not be planned.
PlanErrors []SweepPlanError
// SkippedHeld holds the atoms that DO hold an unclaimed ebuild but are not
// swept because a held entry covers them, sorted.
//
// It exists so the tool cannot contradict itself. The reconciliation ends
// its unclaimed group by pointing at `--clean`, and without this
// field a sweep whose only findings were held would answer "every ebuild is
// claimed by an entry" — which is false twice over: the ebuild is unclaimed,
// and it is being protected rather than overlooked. Skipping quietly is what
// makes a safety rule read as a bug.
SkippedHeld []string
// TotalRemove is the number of files the batch would delete. It counts
// Remove only — a blocked directory's WouldRemove is not pending work.
TotalRemove int
}
SweepBatch is everything an overlay sweep intends to do, ordered so two runs over an unchanged overlay produce the identical plan.
func PlanOverlaySweep ¶
func PlanOverlaySweep(log *slog.Logger, overlayPath string, cfgs map[string]registry.PackageConfig, target string) (SweepBatch, error)
PlanOverlaySweep computes what a sweep would do to every package directory holding an ebuild no entry claims, for the whole overlay or for one target.
The candidates come from Reconcile, which already reports this class of finding (UnclaimedEbuild) with its enabled filter and the bare atom as Key, so what a sweep touches is what `--check` printed — parity by construction, not by two implementations agreeing. The one deliberate break is `hold`, in the reporting direction only: see the filter below.
Why the verdict does NOT come from Reconcile ¶
A divergence says a file is unclaimed; whether removing it is allowed is planSweep's question alone — the live-ebuild rule, the last-non-live floor and both block cases live there, and deleting straight from the divergence list would drop all of them.
Nothing here touches the filesystem beyond reading directories. The registry's reconciliation reports what it skips to log; nil discards it.
type SweepDirPlan ¶
type SweepDirPlan struct {
// Atom is "category/package" — never a registry key. Paths are built from
// it, so a ":slot" or "@label" here would be destructive.
Atom string
// Remove lists the versions to delete, ascending in Gentoo order. Empty on
// any blocked plan.
Remove []string
// Keep maps a kept version to the entry key claiming it. An empty value
// means the version is kept by a rule (the live-ebuild rule or the
// last-non-live floor) rather than by an entry.
Keep map[string]string
// Blocked names the entry that lacks a pin, when there is one to name. A
// directory blocked because NO entry claims it has an empty Blocked — see
// sweepPlan's type comment for why the two are distinguishable.
Blocked string
// WouldRemove lists what a blocked directory left alone, ascending.
WouldRemove []string
}
SweepDirPlan is one package directory's verdict in an overlay sweep.
Exactly one of Remove / WouldRemove carries versions: a blocked directory removes nothing and reports its candidates in WouldRemove instead, so a report can state what an authorised sweep would have done without ever implying it happened.
func (SweepDirPlan) IsBlocked ¶
func (p SweepDirPlan) IsBlocked() bool
IsBlocked reports whether this directory will remove nothing. It is true for both block cases — a pinless entry (Blocked names it) and a directory no entry claims (Blocked is empty) — which is why callers must not test Blocked != "" on its own.
type SweepDirResult ¶
type SweepDirResult struct {
// Atom is the directory, as a bare "category/package".
Atom string
// Removed lists the versions that really went away — never what was merely
// intended. A report that claims a file still on disk is worse than no
// report.
Removed []string
// Kept mirrors the plan's Keep so a report can name the entry claiming each
// surviving version.
Kept map[string]string
// Blocked names the entry that blocked this directory, when there was one.
Blocked string
// WouldRemove lists what a blocked directory left alone.
WouldRemove []string
// Err is this directory's failure. It never aborts the batch.
Err error
}
SweepDirResult is what actually happened to one package directory.
type SweepOption ¶
type SweepOption func(*sweepOptions)
SweepOption configures ExecuteOverlaySweep.
func WithSweepConcurrency ¶
func WithSweepConcurrency(n int) SweepOption
WithSweepConcurrency bounds how many directories are swept at once.
func WithSweepDistdir ¶
func WithSweepDistdir(explicit, configured string) SweepOption
WithSweepDistdir supplies the two configurable rungs of the distdir precedence: explicit is the --distdir flag, configured is autoupdate.distdir from the config file. Both empty is the production default and resolves to the DISTDIR the host itself names.
func WithSweepDistfilesCache ¶
func WithSweepDistfilesCache(dir string) SweepOption
WithSweepDistfilesCache supplies the read-only cache the manifest step reuses already downloaded distfiles from (--distfiles-cache / autoupdate.distfiles_cache). Empty disables the lookup.
func WithSweepExecCommand ¶
func WithSweepExecCommand(fn func(ctx context.Context, name string, arg ...string) *exec.Cmd) SweepOption
WithSweepExecCommand injects the command seam (tests, and nothing else).
func WithSweepLogger ¶
func WithSweepLogger(l *slog.Logger) SweepOption
WithSweepLogger sets the logger the sweep reports its diagnostics to. Nil keeps the default, which discards them.
func WithSweepPackagesConfig ¶
func WithSweepPackagesConfig(cfgs map[string]registry.PackageConfig) SweepOption
WithSweepPackagesConfig supplies the registry, read only for the [meta] block that drives an authenticated distfile fetch.
func WithSweepReporter ¶
func WithSweepReporter(r tui.Reporter) SweepOption
WithSweepReporter routes the Manifest step's streamed output.
type SweepPlanError ¶
SweepPlanError is a directory that could not be planned at all — an unreadable package directory, not a directory with nothing to do. It is reported and dropped from the batch rather than aborting the sweep.
type SweepReport ¶
type SweepReport struct {
// Results holds one entry per planned directory, sorted by Atom.
Results []SweepDirResult
// Removed counts the files that went away across the batch.
Removed int
// Swept counts directories from which at least one file was removed.
Swept int
// BlockedN counts directories that removed nothing because they were
// blocked.
BlockedN int
// Failed counts directories whose removal or Manifest step errored.
Failed int
}
SweepReport is the outcome of an executed batch.
func ExecuteOverlaySweep ¶
func ExecuteOverlaySweep(ctx context.Context, overlayPath string, batch SweepBatch, opts ...SweepOption) SweepReport
ExecuteOverlaySweep runs an approved batch and returns what happened.
Failure isolation ¶
A directory's error is recorded on its own result and never returned as the batch's error. One unreadable directory, one locked file or one failing pkgdev must not strand the other ninety-nine — the sweep's whole purpose is clearing an accumulation, and an all-or-nothing batch would make the accumulation permanent.
Why the default is serial ¶
concurrency defaults to 1. Parallel `pkgdev manifest` runs are unproven against a repository-level lock or the metadata cache, so a caller that forgets to pass a bound gets the safe behaviour rather than the fast one.
type UpdateStatus ¶
type UpdateStatus string
UpdateStatus represents the status of a pending update.
const ( // StatusPending indicates the update has been detected but not yet applied StatusPending UpdateStatus = "pending" // StatusValidated indicates the update was applied and manifest succeeded StatusValidated UpdateStatus = "validated" // StatusFailed indicates the update application failed StatusFailed UpdateStatus = "failed" // StatusApplied indicates the update has been fully applied StatusApplied UpdateStatus = "applied" )
Update status constants
func ValidStatuses ¶
func ValidStatuses() []UpdateStatus
ValidStatuses returns all valid update statuses
type VendorOutcome ¶ added in v0.36.0
type VendorOutcome struct {
Generated bool // a tarball was produced in this run
Tarball string // path of the tarball inside the staged distdir
File, URI string // the vendor distfile name and the URI it is served from
SHA512 string // hex digest of the tarball, computed once after Generate
// ProbeFailed reports a vendor probe that could not answer (a transport
// error, a timeout or a 5xx): whether the tarball is served is unknown, so
// the manifest failure that follows is the vendor step's, not the ebuild's.
ProbeFailed bool
}
VendorOutcome is what the vendor step did for one staged candidate. The zero value means it produced nothing.
Source Files
¶
- agent_progress.go
- analysis_cache.go
- analyzer.go
- applier.go
- applier_adopt.go
- applier_all.go
- applier_check.go
- applier_gates.go
- applier_gates_environment.go
- apply_slots.go
- authfetch_public.go
- batch_result.go
- build_failure.go
- checker.go
- childrun.go
- claims.go
- discovery.go
- doc.go
- fallback.go
- files_version_warning.go
- hostslots.go
- manifest_coverage.go
- manifest_failure.go
- metadata_cache.go
- pending.go
- portage_access.go
- promote.go
- promote_reuse.go
- registry_fix_attempt.go
- requires.go
- revive.go
- script_evaluator_stub.go
- script_parser.go
- sweep.go
- sweep_staged.go
- sweep_vendor.go
- tool_lookpath.go
- vendor_publish.go
Directories
¶
| Path | Synopsis |
|---|---|
|
Package ebuilds reads the ebuild tree of an overlay: it locates a package directory, lists its ebuilds, selects the current ebuild for a slot or a version series, splits package atoms, labels and slots, and extracts EbuildMetadata from an ebuild file.
|
Package ebuilds reads the ebuild tree of an overlay: it locates a package directory, lists its ebuilds, selects the current ebuild for a slot or a version series, splits package atoms, labels and slots, and extracts EbuildMetadata from an ebuild file. |
|
Package fetch is autoupdate's HTTP layer: the retrying client with its circuit breaker, per-host policies and body caps, the rate limiter, the response and fetch caches, header allow-listing with credential scoping, and authenticated distfile fetching driven by a record's meta.fetch_* keys, including the spec parser that --lint reuses.
|
Package fetch is autoupdate's HTTP layer: the retrying client with its circuit breaker, per-host policies and body caps, the rate limiter, the response and fetch caches, header allow-listing with credential scoping, and authenticated distfile fetching driven by a record's meta.fetch_* keys, including the spec parser that --lint reuses. |
|
Package fixer holds autoupdate's agent fixers: LLM agents, run through the Claude Code CLI with the least-privilege profile from llm, that repair a failed bump — the manifest fixer, the build fixer, the bump reviewer and the registry fixer.
|
Package fixer holds autoupdate's agent fixers: LLM agents, run through the Claude Code CLI with the least-privilege profile from llm, that repair a failed bump — the manifest fixer, the build fixer, the bump reviewer and the registry fixer. |
|
Package jsonpath parses the path syntax the autoupdate configuration uses to point at a value inside a JSON document, such as "tag_name", "[0].sha" or "versions[0].version".
|
Package jsonpath parses the path syntax the autoupdate configuration uses to point at a value inside a JSON document, such as "tag_name", "[0].sha" or "versions[0].version". |
|
Package llm holds autoupdate's LLM providers: the OpenAI and Ollama HTTP clients and the Claude Code client that drives the local `claude` CLI, the least-privilege permission profile every agent run is given, and the classification of how a `claude` run ended.
|
Package llm holds autoupdate's LLM providers: the OpenAI and Ollama HTTP clients and the Claude Code client that drives the local `claude` CLI, the least-privilege permission profile every agent run is given, and the classification of how a `claude` run ended. |
|
Package parse extracts versions from upstream content: JSON paths, regular expressions and HTML/XPath, version transforms and series selection, version-history extraction, and the validation of an extracted version against a registry record.
|
Package parse extracts versions from upstream content: JSON paths, regular expressions and HTML/XPath, version transforms and series selection, version-history extraction, and the validation of an extracted version against a registry record. |
|
Package registry is the packages.toml registry: it loads and saves the file, validates each record (including its meta.fetch_* spec, judged by the fetch package's own parser), lints and repairs it, renders a record, prunes and removes records, and migrates auto-disabled entries.
|
Package registry is the packages.toml registry: it loads and saves the file, validates each record (including its meta.fetch_* spec, judged by the fetch package's own parser), lints and repairs it, renders a record, prunes and removes records, and migrates auto-disabled entries. |
|
Package repair holds what an operator-present repair of a failed autoupdate bump needs: the repair bundle written beside a kept staged tree (its schema, its redacted failure text, its atomic write), the lookup that finds it again by package, and the argv of the interactive claude session seeded with it.
|
Package repair holds what an operator-present repair of a failed autoupdate bump needs: the repair bundle written beside a kept staged tree (its schema, its redacted failure text, its atomic write), the lookup that finds it again by package, and the argv of the interactive claude session seeded with it. |
|
Package statefile reads, merges and saves autoupdate's JSON state files under the inter-process file lock, so concurrent bentoo processes never lose each other's writes.
|
Package statefile reads, merges and saves autoupdate's JSON state files under the inter-process file lock, so concurrent bentoo processes never lose each other's writes. |
|
Package validate answers one question about an ebuild without building it: does it still match the source it points at?
|
Package validate answers one question about an ebuild without building it: does it still match the source it points at? |
|
Package vendor handles the vendor tarball of a staged Go candidate ebuild.
|
Package vendor handles the vendor tarball of a staged Go candidate ebuild. |