Documentation
¶
Overview ¶
Package report is the view model of one `overlay autoupdate` run: the packages it scanned, the validation it planned, what the gates answered, and the counts that summarize all of it.
It describes a run, it does not display one ¶
Nothing in this package is a rendering. It holds no ANSI escape sequence, no column width, no padding, no border character and no terminal dimension, and no field describes one. A renderer takes every value it shows from these types, so a fact the model does not carry is a fact no renderer can invent — and shortening a long string is always a rendering decision, never a loss of data.
That separation is the defect this package exists to remove: the check path formatted each section at the moment it printed it, so nothing held "what this run found" as a value, and nothing could be rendered twice, exported, counted or tested without running the command again.
A Section is structure, and structure is not presentation ¶
Section, Table and Row live here, and at first that looks like the rule above being broken. It is not, and the distinction is worth stating because a later reader will ask.
The rule forbids the model from holding PRESENTATION: escape sequences, column widths, padding, borders, terminal dimensions. A Section holds none of those. It holds STRUCTURE — this block is called X, it says these sentences, it has these rows, it notes these omissions. How wide a column is, whether a title is bold, what a border looks like: all still decided in internal/common/report/render, from its own Options.Width and its own lipgloss.Width measurement, neither of which has a field to land in here.
The alternative preserves the letter of the rule and loses its purpose. If Section stayed in render, then render would need a translator per payload, so it would import every domain's types and every new kind of run would edit it — the very coupling this package exists to avoid. The rule exists to keep presentation out of the model; keeping Section in render would instead pull every model into presentation.
It does not depend on the producer ¶
The facts originate in internal/autoupdate and internal/autoupdate/validate, and this package imports neither: a package under internal/common depending on internal/autoupdate would invert the dependency direction. The model takes primitive facts — strings, bools, ints — and an adapter in cmd/bentoo converts the producer's types into them.
The model is built before anything is printed ¶
A run assembles the whole report first, so a run that fails to render, or that is interrupted, still holds a complete description of what it found. Run.Complete and Run.NotEvaluated are how such a run says so, and they sit on the ENVELOPE rather than on a payload: "the run reached the end of its plan" and "this many planned units were never reached" are answerable for a batch of packages, of subvolumes or of ebuilds alike, so a payload that carried them would be carrying them a second time.
Index ¶
- Constants
- type AutoupdateCheck
- type CategoryCount
- type CauseCount
- type ComparePkg
- type CompareRun
- type GateFact
- type KeepGroup
- type Kind
- type ManifestRun
- type ManifestTarget
- type Mode
- type ModeInputs
- type Outcome
- type PackageResult
- type Payload
- type PlanEntry
- type Requirement
- type Row
- type Run
- type Section
- type SectionOptions
- type SnapshotRun
- type SnapshotStep
- type Table
- type Tally
- type ValidationRow
- type VerdictTally
Constants ¶
const SchemaVersion = 2
SchemaVersion is the version number every exported document carries at its root. It is what a consumer reads before it reads anything else.
Why it starts at 2 ¶
This is the first release that declares a version at all, and the number is still 2. That is not an off-by-one. The shape shipped today — the model's own fields at the document root, with no envelope around them — IS schema 1 in the field, even though nothing in it says so. Numbering the new shape 1 would make the version indistinguishable from the version that has no version: a consumer meeting `{"schema": 1, ...}` could not tell whether it held the new document or an old one that had grown a key by coincidence. Starting at 2 leaves 1 meaning exactly what it already means.
Why it is a name and not a literal ¶
Every producer sets Run.Schema, and a 2 typed at each of them is the same defect as a column width typed into a format string: three places to find on the day it becomes 3, and nothing that fails when one is missed.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type AutoupdateCheck ¶
type AutoupdateCheck struct {
// Scanned is every package the run looked at, in the order it looked at
// them, whether or not an update was found. It is what makes a package
// that is up to date distinguishable from a package that was never
// scanned.
Scanned []PackageResult `json:"scanned"`
// Plan is the pending updates the run intended to validate, including the
// ones policy excluded. Dropping those would produce a reassuring tally
// about the easy packages and say nothing about the rest. It is also the
// denominator the tally is reconciled against.
Plan []PlanEntry `json:"plan"`
// Results is what the gates answered, one row per planned package the run
// reached. For an interrupted run it covers only the packages reached
// before the interrupt, and NotEvaluated is how many of the rest there
// were.
Results []ValidationRow `json:"results"`
// Tally is the four counts taken over Plan. It stays HERE while Complete
// and NotEvaluated moved up, and the asymmetry is the line between the two
// halves: this check counts four validation outcomes, a manifest run counts
// ok and failed, and one universal tally would either lose the four or
// invent columns the other has no answer for.
Tally Tally `json:"tally"`
// DistfilesToFetch is how many packages this run has to hold a tarball
// for: an upper bound per package, not a download count, since a distfile
// the host already holds is not fetched again and a package with several
// SRC_URI entries still counts once.
//
// It is the ONE run-level fact that could not be recomputed from anything
// else here. It means "the packages this run takes past the shallowest
// depth", and the model deliberately does not know the depth ladder's
// ordering — Depth is a string spelled the way the flag spells it, with no
// notion of which rung is deeper. So a renderer, or a reader of the JSON
// export, cannot derive it from Plan: the producer must carry it across or
// the number is lost.
//
// It matters because it is the figure an operator decides on. Every depth
// above the shallowest reads the new archive, so on a metered connection
// or a laptop this is what makes the answer no.
DistfilesToFetch int `json:"distfiles_to_fetch"`
}
AutoupdateCheck is everything one `overlay autoupdate check` run found. It is the Payload behind KindAutoupdateCheck.
It is assembled in full before any output is produced. A run that is interrupted still produces one of these; that it WAS interrupted, and how much of its plan it never reached, is said by the Run around it and not here — those two facts are true of any batch, and a payload restating them would be a second place for a finished run to be described as a partial one.
Everything below predates the envelope, unchanged ¶
The field names, the JSON tags and Reconciles are exactly what they were when this type was called Report and sat at the document root. The rename is a move into the payload position, not a redesign: a consumer's `.tally.proved` became `.payload.tally.proved` when the envelope arrived, and nothing here moved it again.
func (AutoupdateCheck) Reconciles ¶
func (r AutoupdateCheck) Reconciles() bool
Reconciles reports whether the tally accounts for every planned package exactly once.
The denominator is the plan, not the result list: a package that produced no row still had to be counted somewhere, and comparing against the rows would make the check pass precisely when packages went missing.
An interrupted run does not reconcile, and that is the honest answer rather than a bug — the packages it never reached are counted in no column, and the envelope's Complete and NotEvaluated are what say so.
func (AutoupdateCheck) Sections ¶
func (r AutoupdateCheck) Sections(opts SectionOptions) []Section
Sections is the autoupdate check's report as structure: what it says, in order, with every value at full length and not one decision about how it will look. It is what makes AutoupdateCheck satisfy Payload.
The knowledge of what a package, a validation gate or --all means lives here, on the payload, rather than in a renderer: a renderer that knew it would have to be edited for every new command, while a payload that describes itself as ordered blocks lets a new command be a new payload. Every syntax reuses these sections, so which packages are up to date or how a skipped entry is labelled is derived once. Nothing is shortened or decorated here: width, escapes and borders are a writer's, so an export with no width budget prints every reason in full.
Only SectionOptions reaches this method, never a Width, so a screen setting cannot leak onto the export path. SkipPlan drops the plan section and nothing else; dropping it here, not in a writer, lets every screen mode inherit the omission so the heading never appears twice. A run that stopped early is labelled by Run.Sections before it delegates here, because "the run reached the end of its plan" is true of any batch, not of this payload.
type CategoryCount ¶
type CategoryCount struct {
// Category is the atom's first half — "dev-lang", "app-editors" — as the
// producer read it, never abbreviated.
Category string `json:"category"`
// Count is how many of the group's members are in that category.
//
// It carries no omitempty, and the zero is reachable in exactly one way: a
// producer that established the entry without establishing its count. That
// is worth being able to see, which is what dropping the key would prevent.
Count int `json:"count"`
}
CategoryCount is one category and how many of a group's members are in it.
Category rather than a bare name because that is the half of the atom being counted, and Count rather than Total because it counts within one group: the run's own totals live on CompareRun, and a name that read like a run-level total here would invite the two to be compared.
type CauseCount ¶
CauseCount is one entry of CompareRun.ReadingFailures: how many failed readings share one cause.
type ComparePkg ¶
type ComparePkg struct {
// Package is the atom, "<category>/<package>", as an operator types it.
//
// The two halves are joined by the adapter rather than carried apart, for
// the reason ManifestTarget.Package gives: every reader of this field
// prints the atom, and carrying the halves separately would leave each
// renderer to re-join them with a separator of its own — two renderers
// spelling one package name differently is the disagreement a model exists
// to make impossible.
Package string `json:"package"`
// Local is the version the overlay carries and Remote the version the
// compared repository carries, each exactly as the producer read it.
//
// Either may be empty, and empty is a fact rather than a gap: a package the
// remote carries no version of has no remote version to state, and a
// placeholder invented here would be this type answering a question the run
// did not. Which of the two is missing is also what OnlyLocal counts one
// level up.
//
// The two are the group key: KeepGroup collects the kept packages whose
// (Local, Remote) pair is identical, so these strings are compared for
// equality rather than parsed. Nothing here or downstream normalises them —
// a version the producer read as "1.2.3-r1" is not the same string as
// "1.2.3", and pretending otherwise would merge two groups that differ.
Local string `json:"local_version"`
Remote string `json:"remote_version"`
// Status is how the two versions relate, in the producer's own vocabulary:
// up to date, outdated, newer, not in remote, or an error reading either.
//
// It answers "how do the versions compare", which is a different question
// from "what should be done about it" — that one is answered by which of
// CompareRun's lists this entry is in, and by Verdicts one level up. The
// producer counts the two axes separately and every compared package is
// counted exactly once on each; deriving one from the other would silently
// change what the other means.
Status string `json:"status"`
// Reading is whether anybody read the difference between the two versions,
// as one of four words: not requested, not comparable, failed, read.
//
// Four values, because a review note's zero value covers reviews disabled,
// no reviewer available, a reviewer that errored, and a finding that was
// never a candidate — so "is the note empty?" cannot answer "did anybody
// read this?", the question an operator asks before acting on a
// recommendation to delete.
//
// It is separate from Diff: one says whether the files differ, the other
// whether anyone explained the difference. The empty string is not one of
// the four; the adapter maps every case to a word, and an unmapped
// combination is a defect, not a blank cell.
Reading string `json:"reading"`
// Diff is what the content check found, drawn from a CLOSED vocabulary:
// "+N/-M" for a measured difference, "identical", "not compared", or
// "unreadable".
//
// The vocabulary is closed so that "no difference was found" can never be
// read as "no comparison was made". Those two are the same blank cell in
// the output this payload replaces, and they are opposite answers: one says
// the overlay's copy is redundant, the other says nobody has established
// anything about it.
//
// It is a formatted string rather than two ints on purpose. The magnitude
// is meaningful only when a comparison ran and found a difference; a pair
// of zeroes would be indistinguishable from a check that never ran, which
// is the conflation the closed vocabulary exists to prevent. The numbers
// the producer measured are what the adapter formats into the first form.
Diff string `json:"diff"`
// Reason is this entry's finding, in one line: why the run recommends what
// it recommends, or why it could not decide.
//
// It is empty when the entry has nothing of its own to add — a package
// whose recommendation follows from its Status and needs no sentence — and
// that emptiness is load-bearing rather than incidental. A kept package
// with an empty Reason carries no finding, and a package carrying no
// finding is one that grouping may absorb; a package with a finding is
// never absorbed into a group, because grouping compresses repetition and a
// finding is not repetition. GroupKeep reads this field for exactly that
// test.
//
// It is ONE LINE by construction, folded by the adapter, because a raw
// newline in a row's detail corrupts both the plain writer and the Markdown
// pipe table. Anything that will not survive being shortened belongs in a
// section's lead or notes, which wrap, rather than in a row detail, which
// is cut. What travels here is the whole folded string at no width budget:
// the export carries every explanation in full, whatever the terminal it
// was rendered at was able to show.
Reason string `json:"reason"`
// FurtherFindings is everything ELSE the run established about this
// package: the second finding and every one after it, in the producer's own
// words and at full length.
//
// Reason holds the FIRST and this holds the rest, because a row detail is
// one line that a narrow device cuts; what does not fit a cell is said
// beside it, in prose that wraps, under the block holding this package's
// row. The package is NOT named in the text — the block names it — so no
// second spelling of Package can disagree with the row beside it.
//
// Every entry is ONE LINE, folded by the adapter, and nothing is shortened.
// Only an empty list says "the run established nothing further", and no
// omitempty drops it: a package with a single finding is the common case.
FurtherFindings []string `json:"further_findings"`
// Cause is why this row failed, in the adapter's closed vocabulary: the
// upstream lookup's cause on a row whose Status is "error", the review's
// cause on a row whose Reading is "failed", "" on a row with no failure.
// No omitempty, for the reason Error gives.
Cause string `json:"cause"`
// Error is the full text of that failure, folded to one line and scrubbed
// of the run's credentials by the producer, and "" on a row with no
// failure. It follows ManifestResult.Error (model.go): kept whole here,
// never shortened; only a table cell may cut it.
Error string `json:"error"`
}
ComparePkg is what the run established about one package: which package, the two versions, how they relate, whether anyone read the difference, what the difference was, and why — in the producer's own words, at full length.
Six strings, and every one of them is already a word rather than a value ¶
Status, Reading and Diff are enums at the producer and strings here, and the conversion is the adapter's. That is not a loss of type safety at this seam: the exported document has no Go types in it, so the choice is only between converting once at the adapter and converting in each renderer plus the exporter. The closed vocabularies those three draw from are stated on the fields below so that the string a reader meets can still be checked against a list.
type CompareRun ¶
type CompareRun struct {
// Repository is the repository the overlay's packages were compared
// against, in the operator's own words — "gentoo" for the ::gentoo tree.
//
// It is carried rather than assumed because a report is read after the run
// that produced it is gone, and "outdated" means nothing without the tree
// it is outdated against. Nothing here abbreviates it, decorates it with
// the :: form or capitalises it: a repository name travels as the producer
// spelled it, and every renderer prints the same string.
Repository string `json:"repository"`
// Scanned is how many packages the run read out of the overlay, InBoth how
// many of those the compared repository also carries, and OnlyLocal how
// many it carries no version of at all.
//
// They are FIELDS because a program holding the exported file cannot call a
// method, and none is derived from another: the producer counts them on
// different axes, so a subtraction here would invent an answer the run never
// gave. A filter flag narrows none of them — a filtered row was evaluated,
// the operator merely chose not to look at it.
//
// None carries omitempty: a dropped zero reads as "the producer never said",
// and "the remote carries every package we do" is the healthy case that
// would lose its zero.
Scanned int `json:"scanned"`
InBoth int `json:"in_both"`
OnlyLocal int `json:"only_local"`
// Redundant is every package the run recommends removing: the overlay's
// copy adds nothing the compared repository does not already carry.
//
// It is the list an operator acts on destructively, which is why the entry
// beside it carries a reading state and a diff cell rather than a bare
// verdict — a recommendation to delete is only as good as the evidence that
// somebody looked.
//
// A nil slice reaches the JSON export as null and an empty one as [], and
// the two say different things: the first is a producer that established
// nothing, the second a run that found nothing redundant. The renderer
// rewrites neither into the other, so the adapter normalises every slice
// here to non-nil and a consumer never has to tell "no rows" from "the
// producer forgot".
Redundant []ComparePkg `json:"redundant"`
// NeedsRebase is every package ::gentoo moved ahead of while the overlay
// carries changes of its own, so ours owes a bump AND a re-application of
// the delta.
//
// It is its own list because its advice is its own: folding it into
// Redundant would recommend deleting changes ::gentoo has no copy of — the
// most expensive mistake this report exists to prevent — and folding it
// into Keep would report the copy as earning its place while it falls
// behind. `overlay compare` already printed this table (verdictSections in
// internal/overlay/compare.go), so leaving the field out would have been a
// regression.
//
// Nil versus empty says what it says on Redundant above.
NeedsRebase []ComparePkg `json:"needs_rebase"`
// Keep is every package the run recommends keeping, in the order the run
// established them.
//
// It is the longest list a healthy overlay produces and the one KeepGroups
// below compresses. The two are not alternatives: this holds every member,
// one entry each, whatever the groups say, so a consumer that ignores the
// groups still reads a complete list and a consumer that reads the groups
// is not left re-deriving the membership it was handed.
//
// The ORDER is information rather than presentation, and nothing here or
// downstream sorts it. What order the run established is the run's to
// decide; a renderer that re-sorted would make two modes disagree about
// what one run said.
Keep []ComparePkg `json:"keep"`
// KeepGroups is the sets of kept packages that share one version pair, as
// the run established them.
//
// It is a field because it is the only place the exported document says a
// large block of entries moves together; re-deriving it from the flat list
// means re-implementing the rule — which pair counts, whether a package
// carrying a finding may be absorbed, how ties break — and two
// implementations of one rule is how a terminal and a file disagree.
//
// A group is never a group of one: grouping compresses repetition rather
// than giving a single package a second name. Groups are ordered by member
// count descending, ties broken by the first member's atom, so two runs over
// one overlay produce byte-identical output.
//
// GroupKeep fills it, and it is the ADAPTER that calls GroupKeep, never
// Sections: a group computed while rows are drawn would exist on a terminal
// and nowhere in the exported file.
KeepGroups []KeepGroup `json:"keep_groups"`
// Unknown is every package the run reached no recommendation about — the
// comparison failed, the remote answered nothing usable, or the package's
// state was never recorded.
//
// It is deliberately its own list rather than an absence. A package that
// fell out of the comparison and a package the comparison had no opinion
// about would otherwise both be "not in any list", and only one of those is
// something an operator should look at. Its entries carry the reason the
// run could not answer, which is what turns a bucket into a finding.
Unknown []ComparePkg `json:"unknown"`
// Verdicts is the four-column tally: how many packages the run recommends
// keeping, removing, rebasing, and how many it could not judge.
//
// It is the payload's own count and not the envelope's, which is the
// asymmetry Run's doc comment states from the other side: "the run reached
// the end of its plan" is answerable for any batch, while a tally is
// counted in each command's own vocabulary and a universal one would either
// lose a column or invent columns another command has no answer for.
//
// It is NOT derivable from the four slices above, and that is the point of
// declaring it. Every scanned package carries a verdict, while the lists
// hold only the packages that also exist in ::gentoo — 99 of the measured
// run's 279 have no row in any of them — so a consumer summing the lists
// answers a number smaller than the run's and has nothing to tell it so.
// The summary section states that coverage gap in words for the same
// reason.
Verdicts VerdictTally `json:"verdicts"`
// Unread is how many comparisons nobody read: a review that was never
// requested, one that was attempted and failed, or a pair the content check
// refused to compare.
//
// It is a run-level count because the per-package fact is already on each
// entry (ComparePkg.Reading) and a total is what an operator reads before
// deciding whether to trust the lists at all. "We checked and it matches"
// and "nobody checked" are the two answers this whole payload exists to
// keep apart; a report that stated the second only per row
// would make an operator scanning 300 lines discover it by accident.
//
// It carries no omitempty for the reason the three counts above do not: a
// run in which everything WAS read is the good case, and the good case is
// the one that would lose its zero.
Unread int `json:"unread"`
// Notes is what the run has to say about ITSELF: the sentences no row
// carries and no count states — the classification share, the baseline
// review's coverage, a review that reached no model at all, the advice to
// prune what is redundant.
//
// They are DATA rather than something the command prints beside the report
// because an export re-derives its blocks from this payload: attached to the
// terminal's sections only, every one of them reached the screen and none
// reached an export, so the file was silently shorter than the screen.
// Carried here, both paths read Sections and Sections reads this.
//
// They are SENTENCES, as ComparePkg.Reason is: a run-level summary names no
// package to travel on, and no set of counts reconstitutes it. What this
// type forbids is a sentence about the DEVICE, and none here mentions one.
//
// Null is a producer that never considered the question, [] a run that had
// nothing to add; the adapter fills it either way, and no omitempty drops
// it — a run with nothing to say still has to say so.
Notes []string `json:"notes"`
// ReadingFailures splits the failed readings among Unread by why each
// review failed: one {cause, count} per cause, over the same population
// Unread counts. The cause is a word the adapter spells, as it
// spells Reading. No omitempty: a run with no failed review publishes an
// empty list, which is the good case.
ReadingFailures []CauseCount `json:"reading_failures"`
}
CompareRun is everything one `overlay compare` run established: the repository the overlay was compared against, how much was scanned and how much the two trees have in common, what the run recommends for each package it has an opinion about, and the tally that summarizes the lot. It is the Payload behind the `overlay.compare` kind.
Every field is a primitive because this package may not import internal/overlay (boundary_test.go's forbiddenImports lists it): conversion happens once, at the adapter in cmd/bentoo, and internal/overlay's enum vocabulary never becomes a wire contract that changes without notice.
KeepGroups is a FIELD, never something Sections computes: a group computed at render time would exist on the terminal and nowhere in the exported file.
Whether the run finished is the envelope's (Run.Complete, Run.NotEvaluated), never restated here. A compare run falls short two ways — interrupted, or finished while failing to establish facts: a review killed, an upstream lookup that errored, a pair the content check refused — and the adapter adds both up; nothing here re-derives either from the counts below.
func (CompareRun) Sections ¶
func (r CompareRun) Sections(opts SectionOptions) []Section
Sections is the compare run as structure: what it says, in order, with every value at full length and not one decision about how it will look. It is also what makes CompareRun satisfy the Payload interface.
Six blocks, because the run answers six questions: how much was compared at all, what may be removed, what needs work re-applied, what earns its place, what nothing is known about, and how it tallies. Redundant leads the verdicts because it is the only destructive one; the summary comes last because a tally read before its evidence is a number nobody can check.
Every block is returned on every path: "nothing is redundant" is an answer, and a missing heading is indistinguishable from a report cut short. Guards read the COUNTS the run established, never the length of a list. Counts go in the Lead; omissions, causes and anything that must be read whole go in the Notes, which wrap — a Row.Detail is cut at the device width, so it holds only a line that still means something shortened.
Only the keep section reads ShowAll, and it changes what is LISTED, never a count. SectionOptions.SkipPlan is not read: none of the six blocks is a plan.
type GateFact ¶
type GateFact struct {
// Deciding reports that this gate participated in the verdict. A gate
// that declined did not, and arrives false.
Deciding bool
// Passed reports that this deciding gate measured the bump and found
// nothing wrong. It says nothing on a gate that did not decide.
Passed bool
// Failed reports that this deciding gate measured the bump and produced
// an error finding.
Failed bool
// Cause is why a gate that did not decide declined: "candidate", "host",
// or "" when its producer never said — validate.DeclineCause's value,
// carried as a plain string so the boundary above stays uncrossed.
//
// Classify does NOT branch on it. It travels so the output can NAME the
// cause, and so the rule has it in hand on the day it tightens.
Cause string
// ProvesSelectedDepth marks the ONE gate whose pass answers the depth the
// policy selected for this bump. At most one fact in a slice carries it.
//
// It is a primitive fact for the same reason Cause is a plain string: the
// depth->gate mapping lives in internal/autoupdate/validate (GateForDepth),
// which a package under internal/common must not import. The adapter
// resolves it and marks the fact; this package only reads the mark.
//
// Nothing carries it when the plan's depth cannot be parsed, or when no
// gate stands for that depth — depth none, today. Both cases fall through
// to Inconclusive rather than guessing a gate.
ProvesSelectedDepth bool
}
GateFact is one gate's contribution to a package's verdict, reduced to the facts the rule reads.
Deciding is "participated", not "was planned" ¶
Deciding answers ONE question: did this gate participate in the verdict. A gate that declined was planned, was asked, and said nothing, so it arrives with Deciding false. That distinction is the whole reason a run that answered cleanly can be told apart from a run that never answered — collapsing the two is the defect this rule exists to remove.
Why primitive facts and not validate.GateResult ¶
The facts originate in internal/autoupdate/validate, and this package must not import it: a package under internal/common depending on internal/autoupdate inverts the dependency direction, and boundary_test.go fails the build the moment that import appears. An adapter in cmd/bentoo converts each GateResult into one of these — PASS/FAILED/SKIPPED into the bools, DeclineCause into the plain string Cause.
type KeepGroup ¶
type KeepGroup struct {
// Label is the group's heading in the reader's own words. It is prose, not
// a key: two groups may share one, and nothing matches on it.
Label string `json:"label"`
// Local and Remote are the version pair every member of this group shares —
// the group's identity, and the only thing that decides membership.
//
// The JSON keys say "version" where the Go names do not, because the two
// names have different readers: `.local_version` is how a consumer asks the
// question, while the Go name sits beside a Label and a Members list where
// no other reading is available. snapshot_run.go's Subvolumes carries the
// same deliberate split in the other direction.
Local string `json:"local_version"`
Remote string `json:"remote_version"`
// Members are the atoms in this group, in the order the run established
// them. The first one breaks ties when two groups have the same member
// count, so this order is part of what makes the report byte-identical
// across two runs over one overlay.
//
// A group always has at least two: a version pair with one member is an
// ordinary row and never a group of one.
Members []string `json:"members"`
// ByCategory is how the group's members break down across categories —
// "42 in dev-lang, 8 in app-editors" as values rather than as a sentence.
//
// It is a field rather than something a renderer counts off Members for the
// reason the whole type is data: the export is read by a program, and a
// breakdown that exists only in a renderer is a breakdown the exported
// document does not carry. It is also what makes a group line useful
// without expanding it — a count alone says how much was compressed, and
// the breakdown says what was compressed.
ByCategory []CategoryCount `json:"by_category"`
}
KeepGroup is a set of packages that share one version pair.
It exists because a report that lists two hundred kept packages one per line tells an operator less than one that says most of them are the same fact repeated. The compression is a fact the run established, carried as data, so the exported document states it too.
A group never hides anything that needs action ¶
Only packages carrying no finding are collected here, and a package with a finding stays its own row whether or not its version pair matches a group's. Every member also remains in CompareRun.Keep, so the group is a view over the list rather than a replacement for it, and dropping the groups costs a reader the summary and no data.
func GroupKeep ¶
func GroupKeep(pkgs []ComparePkg) []KeepGroup
GroupKeep is the rule that fills CompareRun.KeepGroups: the kept packages that share one version pair and carry no finding, collected into groups and ordered so that two runs over one overlay produce the same list.
It is exported because it runs when the payload is BUILT, not when it is drawn: the adapter in cmd/bentoo calls it, and Sections only reads the field, so the exported document carries the groups too. It returns the list rather than filling the field, so the assignment is visible where the payload is built — run.KeepGroups = report.GroupKeep(run.Keep) — and a correctly built payload does not depend on somebody remembering a method call.
A package carrying a finding (a non-empty Reason) is never absorbed, and a version pair with a single member stays an ordinary row; both remain in CompareRun.Keep and compareKeepTable lists them, as it lists every member under --all. Groups come back by member count descending, ties broken by the first member's atom; membership is accumulated in a map, but the order comes from a slice of first appearances, because map iteration is randomised per run. The result is non-nil even when empty: the rule RAN and collected nothing.
type Kind ¶
type Kind string
Kind names what produced a report. It is the JSON discriminator, and the only reason a consumer can tell two exports apart without being told.
A kind names the PRODUCER, never the contents: it is fixed per command and derived from nothing a run establishes, so `.kind == "overlay.manifest"` is a filter rather than a guess. A kind that varied with a run's contents would let a filtering consumer silently miss some of a command's documents; two commands sharing one string would hand it the wrong document under the right name.
Values are dotted (`domain.action`) and no kind may be a prefix of another: `.kind | startswith("overlay.manifest")` is the natural jq for "any manifest run", and a later `overlay.manifest.dry` would match it. run_test.go checks both the equality and the prefix rule, which the compiler cannot.
const ( // KindAutoupdateCheck is `overlay autoupdate check`: the run that scans an // overlay's packages, plans the validation the pending updates earn, and // reports what the gates answered. Its payload carries the four-column // tally. KindAutoupdateCheck Kind = "autoupdate.check" // KindOverlayManifest is `overlay manifest`: the run that regenerates // Manifest files. Its payload counts what succeeded and what failed — // two columns, not four, which is why the tally is a payload's business // and not the envelope's. KindOverlayManifest Kind = "overlay.manifest" // KindSnapshotRun is `snapshot run`: the run that takes and prunes // subvolume snapshots. Its units are subvolumes rather than packages, // which is the distance that proves the envelope carries no domain. KindSnapshotRun Kind = "snapshot.run" // KindOverlayValidate is `overlay validate`: the run that asks whether // each ebuild still matches the source it points at, gate by gate. Its // `--json` emits this envelope, the same document `--export` writes to a // `.json` path, at stdout. // // Its payload is the only one this package does not declare, because of // the dependency direction: the facts a validation run establishes live in // internal/autoupdate/validate, which boundary_test.go forbids this package // from importing, so the payload type sits at the adapter // (cmd/bentoo/overlay_validate_report.go) and stays there. Only the JSON // export carries its fields, reaching them through encoding/json rather // than through Sections. It is on run_test.go's collision sweep because it // is emitted, and an emitted kind is part of the contract consumers filter // on. KindOverlayValidate Kind = "overlay.validate" // KindOverlayCompare is `overlay compare`: the run that holds every // package the overlay carries against the copy a chosen repository ships, // and reaches a recommendation about each one. Its payload carries the // repository compared against, how much was scanned and shared, the // packages, and the four-column verdict tally. // // Its payload's lists PARTITION what the run found: four DISJOINT sets, // one per recommendation (remove ours, re-apply ours, keep ours, no // opinion), unlike the check's slices, which are stages over one set. They // are four fields rather than one list with a verdict column because // folding the rebase list into the redundant one would recommend deleting // an ebuild carrying changes the compared repository has no copy of. // // The value is FLAT as the command grows sub-flows: a later // `overlay.compare.realign` would break the prefix rule above, so every // sub-flow shares this kind and says which flow it was through the // payload. `overlay.manifest` and `overlay.validate` are SIBLINGS, not // prefixes, which is what the dotted form is for. KindOverlayCompare Kind = "overlay.compare" )
type ManifestRun ¶
type ManifestRun struct {
// Targets is every package the run was given, in the order it was given
// them, whatever became of each.
//
// A nil slice reaches the JSON export as null and an empty one as [], and
// the two say different things: the first is a producer that established
// nothing, the second a run that was handed no target at all. Neither is
// rewritten into the other on the way out.
Targets []ManifestTarget `json:"targets"`
// Ok is how many targets had their Manifest regenerated, and Failed is how
// many did not.
//
// They are FIELDS although overlay.ManifestResult derives the same answers
// with methods: that type is read by Go code, this one by a program holding
// the exported document, and a count that exists only as a method is a
// count the JSON export does not carry.
//
// They are established by the run, never summed from Targets: a dry run
// marks every previewed target Success false, so a sum would report every
// package failed in a run that invoked nothing. The adapter branches on the
// preview BEFORE it counts, as Run.Complete's fact travels from the run.
//
// Neither carries omitempty: a dropped zero reads as "the producer never
// said", and "no target failed" is the common case that would lose it.
Ok int `json:"ok"`
Failed int `json:"failed"`
// DryRun reports that the run was a preview: the targets below were
// resolved and listed, and nothing was invoked for any of them.
//
// It is a fact about what the run DID, not about how it is displayed —
// which is what earns it a place in a model that holds no presentation. It
// is also the one thing that explains the shape of everything else here: on
// a preview Ok and Failed are both zero while Targets is full, and without
// this field a reader meeting those three numbers would have to guess
// whether the run failed silently or never started. Sections states it in
// words for the same reason.
DryRun bool `json:"dry_run"`
}
ManifestRun is everything one `overlay manifest` run established: the targets it was given, what became of each of them, and the two counts that summarize the lot. It is the Payload behind KindOverlayManifest.
The type and its Sections share one file, unlike AutoupdateCheck, whose fields in model.go predate the Payload interface: one file per payload is what makes "a new kind of run is a new file" true, instead of a model.go that grows one struct per command.
Its tally has two columns because a target either had its Manifest regenerated or not; one universal tally on the envelope would have to lose the check's four or invent columns this run has no answer for. Whether the run finished is the envelope's (Run.Complete, Run.NotEvaluated) and is absent here, so a finished run cannot be described as a partial one twice over.
func (ManifestRun) Sections ¶
func (r ManifestRun) Sections(opts SectionOptions) []Section
Sections is the manifest run as structure: what it says, in order, with every value at full length and not one decision about how it will look.
It is one block because the run establishes one thing: for each target, was the Manifest regenerated. A second section would be a heading that says nothing the first does not, and padding trains a reader to skim.
ShowAll decides what is LISTED and never what is counted: a target that succeeded is listed only when asked, the count comes from the run's own Ok so it cannot move with the listing, and a note states that the list is short and why. A FAILURE is always listed, whatever the flag, because it is why the operator is reading the report. A preview lists every target and never reads ShowAll: on a dry run the names ARE the finding.
type ManifestTarget ¶
type ManifestTarget struct {
// Package is the atom, "<category>/<package>", as an operator types it.
//
// The two halves are joined by the adapter rather than carried apart,
// because every reader of this field prints the atom: carrying them
// separately would leave each renderer to re-join them with a separator of
// its own, and two renderers spelling one package name differently is the
// disagreement the model exists to make impossible.
Package string `json:"package"`
// Success reports that this target's Manifest was regenerated.
//
// It is false for a target that was merely previewed, and that reading is
// the producer's: "did not succeed" covers "never attempted". Which of the
// two happened is said by DryRun above, once for the run, rather than by a
// third state per target — a distinction the run makes once cannot drift
// between rows.
Success bool `json:"success"`
// Error is why this target failed, in the producer's own words, and empty
// for a target that did not fail.
//
// It carries no atom: Package is right beside it, and a copy of the atom
// in here would be printed twice on every failure line.
Error string `json:"error"`
// Output is what the failing command printed, verbatim — the diagnostic the
// operator acts on, kept because by the time a report is rendered the
// terminal that streamed it live is gone.
//
// It is populated only on failure, and that is the producer's decision
// rather than this type's: the capture is unbounded, and a whole-overlay
// run would otherwise hold one copy per target for no reader. Sections
// states that omission rather than leaving a reader to wonder where a
// successful target's output went.
Output string `json:"output"`
}
ManifestTarget is what the run established about one package.
type Mode ¶
type Mode string
Mode is how a run renders itself: the four values the --ui flag, the BENTOO_UI environment variable and the ui.mode configuration key all accept. The same four words in all three places, so a value that works in one works in the others.
Why a UI mode lives in the MODEL package ¶
A mode is configuration data — a string with four legal values — not formatting. It carries no width, no colour, no escape sequence and no terminal dimension, so it crosses none of the presentation rules boundary_test.go enforces.
Keeping it here is what lets the renderer and internal/common/tui both depend on the resolution without depending on each other: the answer to "which renderer" cannot live inside one of the renderers.
const ( // ModeAuto is a question — "decide for me" — not an answer. It is legal // as an INPUT from any source, and ResolveMode never returns it: a // renderer cannot render a question. ModeAuto Mode = "auto" // ModePlain is line-by-line output with no cursor control. It is what a // pipe, a log file and a CI job get, and it is the fallback whenever a // richer mode was asked for and cannot be delivered. ModePlain Mode = "plain" // ModeInline redraws in place inside the normal scrollback, leaving the // run's output behind when it finishes. It needs a terminal. ModeInline Mode = "inline" // ModeFullscreen takes over the whole terminal for the duration of the // run. It needs a terminal, and it is opt-in only: taking over // someone's screen is never the consequence of configuring nothing. ModeFullscreen Mode = "fullscreen" )
func ResolveMode ¶
func ResolveMode(in ModeInputs) (Mode, string, error)
ResolveMode returns the effective mode and, when it had to downgrade, one sentence for stderr. It never returns ModeAuto.
Precedence: the --ui flag, then BENTOO_UI, then ui.mode, then auto; the first source that speaks decides. With nothing configured it reproduces tui.Enabled exactly (NoTUI gives plain, auto gives inline on a terminal and plain off one), so a user who set no ui.mode sees no change. auto never yields fullscreen: taking over the screen is opt-in.
Every stated source is validated, not just the deciding one, so a typo that is merely outranked today cannot decide a run tomorrow; the error names its source. An outranked refusal is a WARNING, never an error, so a stale BENTOO_UI cannot override the flag just typed. --no-tui is a boolean opt-out, not a source, so `--ui=bogus --no-tui` is still rejected; it outranks an explicit --ui because it also carries NO_COLOR and BENTOO_NO_TUI, and an opt-out a flag could override would not be one.
The sentence is returned, never printed: the caller writes it to stderr once per run. "" means the run got what it asked for.
type ModeInputs ¶
type ModeInputs struct {
// Flag is the --ui value, empty when the flag was not passed.
Flag string
// Env is the BENTOO_UI value, empty when the variable is unset. Per the
// same convention tui.Enabled follows, an empty value means "not set".
Env string
// Config is the ui.mode configuration value, empty when the key is
// absent. Absent must produce exactly today's behaviour.
Config string
// NoTUI is the opt-out layer, and the CALLER MUST FOLD ALL THREE OPT-OUTS
// INTO IT:
//
// NoTUI: noTUIFlag ||
// os.Getenv("BENTOO_NO_TUI") != "" ||
// os.Getenv("NO_COLOR") != ""
//
// All three, not just the flag. tui.Enabled returns false on THREE
// opt-outs — the --no-tui flag, NO_COLOR and BENTOO_NO_TUI — and this
// struct deliberately has no NO_COLOR field of its own. Drop NO_COLOR
// from that expression and a user who set it, and configured nothing
// about ui.mode, silently starts getting inline output where they get
// plain today: precisely the change an unconfigured run must never see.
//
// It is applied as ModePlain at the flag layer, so --no-tui is an
// alias for --ui=plain rather than a second mechanism competing with it.
NoTUI bool
// Interactive is the stdout-TTY answer ALONE — output.IsTerminal(), the
// same probe tui.Enabled ends on.
//
// It is NOT tui.Enabled's full return value. That answer already folds
// the opt-outs in, and folding them in twice would report an opted-out
// run as a terminal that "cannot support" the mode, which is a false
// sentence: the terminal was fine, the operator opted out. The opt-outs
// go in NoTUI; the capability goes here.
Interactive bool
}
ModeInputs is every input the mode decision reads, taken AS DATA.
ResolveMode does not read the environment, and that is the point ¶
There is no os.Getenv in this file. Env and Interactive are supplied by the caller, which is what makes the whole precedence matrix testable without a TTY and without mutating the environment of a running test binary — the reason this struct exists at all instead of four positional arguments.
The environment reading belongs at the edge, in cmd/bentoo, next to the flag definitions.
type Outcome ¶
type Outcome string
Outcome is what a run managed to establish about one planned package.
There are four values rather than three because "we could not evaluate this" and "you told us not to evaluate this" are different answers, and folding them together lets a defect in the toolkit hide behind the operator's own policy. Which of the four a package earns is decided from a typed cause carried by the validation result, never by matching text in a human-readable reason.
const ( // Proved means every deciding gate answered for the package and all of // them passed. It is the only value that stands for "this bump was // measured and it survived". Proved Outcome = "proved" // Errored means at least one deciding gate ran and failed. Something was // measured, and what it measured was wrong. Errored Outcome = "errored" // Inconclusive means no deciding gate answered because THE TOOLKIT could // not evaluate the package — an unsupported build system, an unpreparable // tree, a missing dependency. A package whose result records no // cause at all lands here too, so an unclassified case stays visible // instead of being absorbed into policy. Inconclusive Outcome = "inconclusive" // Skipped means no gate was run because POLICY said not to run one — a // configured depth of none, a package type the run excludes. It is // the operator's own decision reported back, not a limitation. Skipped Outcome = "skipped" )
func Classify ¶
Classify decides which of the tally's four columns one planned package lands in. The order IS the rule:
policy said no → Skipped (FIRST: the operator outranks any gate) any deciding failed → Errored every gate passed → Proved selected depth passed → Proved (only after the lines above) gates all declined → Inconclusive no cause recorded → Inconclusive (deliberate: do not "fix" it)
Inputs are typed facts, never reason text, so rewording a reason moves no package. A decline with no Cause fails open to Inconclusive, like validate.DeclineUnrecorded: Skipped would hide a toolkit failure behind the operator's policy, so a large Inconclusive column is expected until producers tag a cause. Proved needs a deciding gate; no package WorstOutcome called errored becomes proved. Callers pass only deciding gates, dropping pkgcheck as WorstOutcome does, or a host without pkgcheck turns every Proved Inconclusive.
type PackageResult ¶
type PackageResult struct {
// Package is the full atom, category/package.
Package string `json:"package"`
// Type is the tier the package resolved to, "bin" or "source". It is empty
// when the current ebuild could not be read, and empty is meaningful:
// nobody resolved it, so nothing may be assumed from it.
Type string `json:"type"`
// CurrentVersion is the version the overlay holds today.
CurrentVersion string `json:"current_version"`
// CandidateVersion is the version found upstream. It is present even when
// it could not be ordered against CurrentVersion, because the unusable
// value is exactly what a reader needs to fix the parser configuration.
CandidateVersion string `json:"candidate_version"`
// HasUpdate reports that CandidateVersion is newer than CurrentVersion. It
// is false whenever the two could not be compared.
HasUpdate bool `json:"has_update"`
// NotComparable reports that the upstream value could not be ordered
// against the current one. It is carried apart from HasUpdate so that a
// broken parser is never read as "up to date".
NotComparable bool `json:"not_comparable"`
// Orphaned reports that the package no longer has any ebuild in the
// overlay. When set, the version fields say nothing.
Orphaned bool `json:"orphaned"`
// FromCache reports that the upstream version was answered from cache
// rather than fetched, which is why the run may not reflect a bump made in
// the last few minutes.
FromCache bool `json:"from_cache"`
// Error is why the scan could not answer for this package, in full and
// exactly as it was reported. It is empty when the scan succeeded, and a
// string rather than an error because the model carries facts and has no
// behaviour.
Error string `json:"error"`
// Requirements is the state of each package this bump requires (the
// record's `requires`), in atom order, and empty for a record without them.
// Always a key, like every field here: an absent key could not be told from
// an unanswered one.
Requirements []Requirement `json:"requirements"`
}
PackageResult is what the run learned about one scanned package, before any validation is planned.
A scan reports ONE condition per package — orphaned, failed, not comparable, or an update found — so those four are mutually exclusive by construction even though nothing in the type says so, and all four false means the package was read and is already up to date. FromCache is orthogonal to them: it qualifies where the upstream answer came from, not what the answer was.
type Payload ¶
type Payload interface {
// Sections returns the payload's blocks in the order they are meant to be
// read. opts says what the report should SAY; nothing in it says how the
// report should look.
Sections(opts SectionOptions) []Section
}
Payload is the domain half: the facts one kind of run establishes, and how that kind says them as ordered blocks.
One method, and it is the entire contract between a producer and every renderer. A payload turns itself into []Section — titled blocks of structure, with no width, no colour and no escape sequence in them — and each renderer consumes sections and nothing else. That is the seam: a new kind of run implements this interface, and no renderer changes.
Sections is the only way anything in this package reaches a payload. The payload does also MARSHAL — encoding/json reaches its exported fields directly, which is what puts the domain facts under "payload" in the exported document — but no code here and none in render may branch on its concrete type. A type switch over payloads is the edit-per-kind this interface exists to prevent.
type PlanEntry ¶
type PlanEntry struct {
// Package is the full atom, category/package.
Package string `json:"package"`
// CurrentVersion and CandidateVersion are the two ends of the bump. Both
// are carried because the depth follows the distance between them, and a
// reader checking the plan against the policy needs the two values the
// classifier saw.
CurrentVersion string `json:"current_version"`
CandidateVersion string `json:"candidate_version"`
// Class is how far the bump moved — how the version distance was
// classified.
Class string `json:"class"`
// Depth is the resolved depth, spelled the way the --depth flag and the
// config key spell it, so a plan line can be typed back as a flag.
Depth string `json:"depth"`
// Reason names the input that decided the depth, in full, and quotes an
// override's stated justification where one exists. It is never shortened
// here; a renderer that lacks the room shortens its own copy.
Reason string `json:"reason"`
// Skipped marks an entry no gate will run for, because policy said so — a
// depth of none, or a package type the run excludes. It travels with the
// entry rather than being derived when the entry is shown, because the
// reason has to travel with it, and because it is what separates Skipped
// from Inconclusive in the tally.
Skipped bool `json:"skipped"`
}
PlanEntry is one pending update the run intends to validate: the bump, the depth it earned, and the case for that depth.
The plan is what the operator approves before any gate spends time or bandwidth, so an entry has to carry the case for its own cost. Reason is never expected to be empty: an entry whose reason went missing leaves a number nobody can check.
type Requirement ¶
type Requirement struct {
Package string `json:"package"`
Version string `json:"version"`
State string `json:"state"`
}
Requirement is one package a bump requires and whether its version is available yet: "present" (the overlay or ::gentoo has it), "pending" (another pending bump will provide it) or "missing".
type Row ¶
type Row struct {
// Cells are the record's values, one per column, in header order. A row
// may hold fewer than the headers name — a value the run never
// established — and it may hold more; Columns is what keeps the surplus
// from being dropped.
Cells []string
// Detail is the row's reason, IN FULL. A writer with a width budget cuts
// its own copy; a writer without one prints all of it.
//
// Empty means this row has nothing of its own to add, which is a decision
// about content and not a sign that the report held no reason: a result
// whose reason repeats its plan entry leaves this empty so the sentence is
// printed once, while the report itself still carries the whole string.
// Every writer omits an empty detail rather than printing a bare indent.
Detail string
}
Row is one record: its cells, and the explanation that belongs to it.
type Run ¶
type Run struct {
// Schema is the document's version, always SchemaVersion. It is written
// even though every document this release produces carries the same
// number, because a version a consumer has to infer from the keys present
// is the situation schema 1 is already in.
Schema int `json:"schema"`
// Kind names the command that produced this document. It is what a
// consumer branches on, and the only key that says what the payload
// below is.
Kind Kind `json:"kind"`
// Title is the run's heading in the reader's own words — "Autoupdate
// check", "42 targets". It is a label, not a discriminator: two runs may
// share one, and a consumer that matched on it would be matching on prose.
Title string `json:"title"`
// Complete reports that the run reached the end of its plan. A run stopped
// early — by an interrupt, or by a failure it could not continue past —
// sets this false, and the report still holds everything established up to
// that point.
//
// It has no omitempty, and that is load-bearing: a `false` dropped from the
// document reads as "the producer never said", which is exactly the
// conflation this field exists to remove.
Complete bool `json:"complete"`
// NotEvaluated is how many planned units the run never reached. It is zero
// for a complete run, and for an interrupted one it is the number that
// turns a short list into a stated gap rather than a silent one.
//
// It carries no omitempty for the same reason Complete does not.
NotEvaluated int `json:"not_evaluated"`
// Payload is the domain half, and it marshals NESTED under its own key.
// The nesting is the whole point of the envelope: `.tally.proved` becomes
// `.payload.tally.proved`, and every kind added after this one adds a
// value to Kind rather than a key at the root. A payload flattened up
// here would be schema 1 again, with Kind saying nothing about the shape
// beside it.
Payload Payload `json:"payload"`
}
Run is what every report carries, whatever produced it: the envelope.
It is assembled in full before any part of it is rendered, so a run that is interrupted, or that fails to write its output anywhere, still holds a complete description of what it established.
The envelope knows nothing about the domain: every field is true of ANY batch, and the facts of one kind of run sit behind Payload, reachable only through Sections, so a new kind of run is a new payload rather than a renderer edit. Complete and NotEvaluated are here because any batch can answer them; a tally is not, because each payload counts in its own columns (four outcomes for autoupdate, ok and failed for manifest).
It marshals and does not unmarshal, on purpose: decoding into the Payload interface needs a hand-kept registry of every kind, and export is write-only today. The reader belongs with the first use case that needs one, which should say whether it needs a round trip, one field or only the kind.
func (Run) Sections ¶
func (r Run) Sections(opts SectionOptions) []Section
Sections is the whole run as ordered blocks: the gap it left, if it left one, and then everything its payload has to say.
The envelope states the gap because only the envelope holds Complete and NotEvaluated, and one sentence here serves packages, subvolumes and ebuilds alike. It goes FIRST: every block below is short by the units never reached, a reader must meet the qualifier before the tables, and the fullscreen viewport cuts from the bottom. It is prepended to a NEW slice, because appending into the payload's own would let displaying a report edit the blocks the export then writes.
A nil Payload (a producer that returned early) contributes no block rather than panicking: a report is what an operator gets INSTEAD of a crash, and an incomplete run still states its gap.
type Section ¶
type Section struct {
// Title names the block. It is the only string a writer may decorate.
Title string
// Lead is what is said before the rows — the counts that tell a reader
// what they are about to look at.
Lead []string
// Rows is the block's table. A section with no rows prints its lead alone,
// which is how "nothing to validate" is said without an empty frame.
Rows Table
// Notes is what is said after the rows: what was left out and why, and
// what the run did not do. An omitted row is stated here, never silent.
Notes []string
}
Section is one titled block of a report: a heading, the sentences that frame it, the rows themselves, and the sentences that qualify them.
It carries structure, never presentation ¶
Every field answers "what does this block SAY" and none answers "how does it look": no width, colour, padding, border or terminal dimension. The strings are the model's own, at full length, so the same Section prints as an 80-column table, a pipe table or a pane inside a box without any renderer being able to change what the block says.
A section with no rows is a section, not an absence ¶
"Nothing to validate" is said by a Section with a Lead and an empty Table — the sentence is the content, and dropping it once the table came back empty would make an omission indistinguishable from a block that was never produced.
type SectionOptions ¶
type SectionOptions struct {
// ShowAll lists the units a run found nothing to report about — the
// packages already up to date — instead of only counting them. It is the
// --all flag.
//
// False prints the count alone, and that is the default because those
// units are the bulk of a large run: listing them unasked is most of why a
// check that found four updates printed 348 lines.
ShowAll bool
// SkipPlan omits the section describing what the run intended to do. It
// exists for exactly one caller: the on-screen report of a run whose plan
// was already printed before the confirmation prompt, so the operator is
// not shown the same list twice.
//
// An export must never set it. A record missing the plan answers no
// question later, and the reader of the file is not the operator
// who saw the plan go by on screen.
SkipPlan bool
}
SectionOptions is what a report should SAY. render.Options is what the device ALLOWS. Keeping those two apart is the split this package exists to hold.
Listing every package a run found up to date is a decision about CONTENT, and it is made once, here, where the sections are built. Deciding that a line may be 100 cells wide is a decision about the DEVICE, and it is made in a renderer, from that device's own measurement. A renderer that read ShowAll would be deciding content, and the same run would then say different things in plain and in fullscreen with nothing in the model able to explain why.
It lives beside Payload rather than beside Section because it is what a payload is ASKED, not part of what a payload answers.
type SnapshotRun ¶
type SnapshotRun struct {
// Subvolumes is every subvolume the run set out to operate on, in
// configuration order, whatever became of each.
//
// The JSON key is singular on purpose: it names the question a consumer
// asks — "which subvolume did this run operate on?" — while the Go name
// names what the field holds, a list, because a run covers every subvolume
// its configuration names.
//
// It is the PLAN, and Steps below is what the run reached; neither is
// derived from the other. A failed create skips that subvolume's remaining
// steps and a cancelled run never produces steps for the rest, yet this
// field still names every subvolume, which is what lets a reader see WHICH
// one is missing — the envelope's NotEvaluated says only how many.
Subvolumes []string `json:"subvolume"`
// Steps is every pipeline step that reached an outcome, in the order the
// run established them.
//
// A nil slice reaches the JSON export as null and an empty one as [], and
// the two say different things: the first is a producer that established
// nothing, the second a run that ran no step at all. Neither is rewritten
// into the other on the way out.
//
// The ORDER is information rather than presentation — "prune failed after
// create succeeded" and "create failed and prune never ran" are different
// runs, and position is the only thing that distinguishes them in a flat
// list — so nothing here or downstream sorts it.
Steps []SnapshotStep `json:"steps"`
// Ok is how many steps did what they were asked, and Failed is how many did
// not.
//
// They are FIELDS although the rows below could be walked to re-derive
// them, for the reason ManifestRun.Ok gives at length: this document is
// read by a program holding the exported file, and a program holding a file
// cannot call a method. A count that exists only as a method is a count the
// JSON export does not carry.
//
// Neither carries omitempty, and that is load-bearing: a zero dropped from
// the document reads as "the producer never said", which conflates "no step
// failed" with "nobody counted" — and a run in which everything succeeded
// is the common case that would lose its zero.
Ok int `json:"ok"`
Failed int `json:"failed"`
}
SnapshotRun is everything one `snapshot run` established: the subvolumes it set out to operate on, the steps it ran over them, and the two counts that summarize the lot. It is the Payload behind KindSnapshotRun.
Not one field is a package, a version, an ebuild or an atom, and Run did not grow to carry them: everything domain-specific — a subvolume, a pipeline step, a ship target — is here, so the envelope never mentions btrfs.
The manifest counts REGENERATED targets and this counts SUCCEEDED steps. The arity matches by coincidence; a universal tally on the envelope would make one command lie in the other's vocabulary, which is why these two ints are not reused from ManifestRun.
Whether the run finished is the envelope's (Run.Complete, Run.NotEvaluated); restating it here would be a second place for a finished run to be described as a partial one.
func (SnapshotRun) Sections ¶
func (r SnapshotRun) Sections(opts SectionOptions) []Section
Sections is the snapshot run as structure: what it says, in order, with every value at full length and not one decision about how it will look.
One block, because the run establishes one thing ¶
A snapshot run answers one question — for each step, did it do what it was asked — and a second heading would sit above content the first already carries. A report padded with empty blocks trains a reader to skim past the one that matters.
ShowAll decides what is LISTED and never what is counted ¶
A successful step is listed when SectionOptions.ShowAll asks and counted otherwise, with the omission stated in words. The count comes from the run's own Ok, never from the rows, so the number cannot move when the listing does. A FAILED step is always listed: it is the reason an operator reads a snapshot report at all.
type SnapshotStep ¶
type SnapshotStep struct {
// Subvolume is the subvolume this step operated on.
//
// It may be empty, and empty is a fact rather than a gap: a step that
// sweeps a remote holding objects from every subvolume is not attributable
// to one, and the producer leaves it blank to say so. Nothing here invents
// a placeholder for it — a made-up "all" would be this type answering a
// question the run did not.
Subvolume string `json:"subvolume"`
// Step is what the run was doing: create, prune, ship or gfs, in the
// producer's own words.
Step string `json:"step"`
// Target is the ship destination this step served, and empty for a step
// that serves no destination.
//
// It is the destination's NAME — the one configured for it, or the driver
// word when it was left unnamed — and never its address. A ship target's
// address carries a host, a user and a remote path, and this value travels
// into a file an operator may attach to a bug report.
//
// That rule covers Subvolume, Step and Target, each a name this package
// controls the shape of. It does NOT cover Error, which is producer stderr
// taken verbatim and says so itself; claiming the whole type address-free
// would be a false safety claim about the field most likely to carry one.
Target string `json:"target"`
// Success reports that the step did what it was asked.
//
// It is derived once, by the adapter, from the producer's own status word,
// and the payload's counts are taken from THAT SAME expression — so the
// word printed on a row and the number printed above it cannot describe
// different runs.
Success bool `json:"success"`
// Error is why this step failed, in the producer's own words, and empty for
// a step that did not fail.
//
// It carries the failing command's own stderr, taken verbatim and never
// reworded: a rewritten diagnostic is one the operator cannot search for or
// reproduce.
//
// VERBATIM CUTS BOTH WAYS: this field is the exception to Target's
// no-address rule. A failed `btrbk ssh` send writes `user@host:/path`, so a
// remote address CAN reach an export through here, and nothing scrubs it.
// An export that includes a FAILED ship step should be read before it is
// shared; redaction, if ever wanted, belongs at the producer that knows
// which text is an address.
//
// It carries no subvolume and no step name: both sit beside it in this same
// row, and a copy of either in here would print twice on every failure.
Error string `json:"error"`
}
SnapshotStep is what the run established about one step of its pipeline.
A step here is a STAGE, not a subprocess ¶
The two are different granularities and only one of them is legible. A stage is create, prune or ship: the thing the run set out to do, named by the manager that sequences them. A subprocess is `btrbk` or `snapper`, and one stage may invoke none of them, one, or several — the ssh ship moves no bytes itself, and a snapper subvolume takes several calls to create one snapshot. A report built from subprocesses would print "snapper, snapper, snapper" and leave the operator to guess which was the prune, which is why the step this type carries is the caller's semantic one.
type Table ¶
type Table struct {
// Headers names each column. It may be empty — a table of bare rows is
// still a table — and it may be shorter than a row is wide, which is what
// Columns exists to survive.
Headers []string
// Rows are the records, in the order the run established them. Nothing
// here is sorted, shortened or padded: order is a fact about the run, and
// the other three are decisions a writer makes.
Rows []Row
}
Table is a header and its rows, in the order they will print.
func (Table) Columns ¶
Columns is how many columns the table has, taken over the header AND every row rather than from the header alone, so a row carrying an extra cell is printed rather than silently dropped.
The difference is not academic. A count read off the headers answers 2 for a table whose rows carry three cells, and the third cell then falls off the right-hand edge of every renderer at once — with no golden file to show it, because the value was never rendered anywhere to be compared. Taking the widest thing the table holds makes the extra cell visible instead.
It lives on the model rather than in a renderer for the same reason: the two syntaxes must not be able to disagree about how many columns a table has.
type Tally ¶
type Tally struct {
// Proved counts the packages whose deciding gates all passed.
Proved int `json:"proved"`
// Errored counts the packages with at least one failing deciding gate.
Errored int `json:"errored"`
// Inconclusive counts the packages the toolkit could not evaluate, plus
// the ones whose results named no cause. Both are limitations of the
// toolkit rather than choices of the operator.
Inconclusive int `json:"inconclusive"`
// Skipped counts the packages policy said not to evaluate. "Nothing was
// said" is never folded into Proved.
Skipped int `json:"skipped"`
}
Tally is the run's four counts, one per Outcome.
It divides the third column of the earlier three-column tally: proved and errored are untouched and count exactly the packages they counted before. The invariant that column set protected is preserved — each planned package lands in exactly one column, and the columns sum to the number of planned packages (AutoupdateCheck.Reconciles).
type ValidationRow ¶
type ValidationRow struct {
// Package is the full atom, category/package.
Package string `json:"package"`
// CandidateVersion is the version that was evaluated.
CandidateVersion string `json:"candidate_version"`
// Outcome is the one verdict that stands for the whole package, across
// every gate that spoke for it.
Outcome Outcome `json:"outcome"`
// Depth is how far validation actually got, which is not always how far it
// was asked to go — PlanEntry.Depth is the request. Both are needed to say
// "compile was asked for, configure is as far as it got", because an
// outcome has to name its own reach.
Depth string `json:"depth"`
// Reason is why this package earned this outcome, IN FULL: the gate's own
// reason where there is one, otherwise the reason the run reported for the
// depth or the plan. It is never shortened, never truncated and never
// summarized in the model — that is a rendering decision, and doing it
// here would make it a loss of data.
//
// A row that produced no verdict always carries a reason: a package
// reported without one reads as a result.
Reason string `json:"reason"`
// SameReasonAsPlan reports that Reason repeats the plan entry's reason
// word for word. The run sets it; a renderer uses it to print a reason
// once instead of twice, and a differing reason is new information
// that gets printed. It is computed where the two strings are both
// in hand rather than by a renderer that would have to go looking for the
// plan entry.
SameReasonAsPlan bool `json:"same_reason_as_plan"`
// Notes are facts about how the outcome was reached that no gate carries,
// such as a vendor tarball generated for this check. They are printed with
// the row whether or not its reason is.
Notes []string `json:"notes"`
}
ValidationRow is what the gates had to say about one planned package.
One row per package the run reached, and each row lands in exactly one column of the Tally.
type VerdictTally ¶
type VerdictTally struct {
// Keep is how many packages the run recommends keeping.
Keep int `json:"keep"`
// Redundant is how many the run recommends removing, because the overlay's
// copy adds nothing the compared repository does not already carry.
Redundant int `json:"redundant"`
// NeedsRebase is how many carry changes of ours on top of a version the
// compared repository has since moved past: the overlay's work is worth
// keeping and has to be re-applied.
NeedsRebase int `json:"needs_rebase"`
// Unknown is how many the run reached no recommendation about. It is never
// folded into any of the three above: a package nothing is known about must
// not be counted as one recommended for removal, which is the rule the
// producer's own verdict table is written to guarantee.
Unknown int `json:"unknown"`
}
VerdictTally is the four-column count of what the run recommends: keep, remove, rebase, and no opinion. It mirrors the four counters the producer maintains, one per verdict, and adds nothing to them.
It is a payload's own count because each command counts in its own vocabulary — four validation outcomes, two manifest columns, two snapshot columns, four verdicts — and one universal tally on the envelope would lose or invent columns.
It is counted by the run, never summed from CompareRun's lists: a filter flag narrows what an operator looks at and narrows no counter, and a second answer to one question is the first place the two could disagree.
No field carries omitempty. A zero dropped from the document reads as "the producer never said", which would let an unexamined overlay look like a clean one.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package render turns the view model in internal/common/report into text for a terminal: it decides how wide a column is, how a value that does not fit is cut, and how much room the line has to begin with.
|
Package render turns the view model in internal/common/report into text for a terminal: it decides how wide a column is, how a value that does not fit is cut, and how much room the line has to begin with. |