dogfood

package
v1.2.21 Latest Latest
Warning

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

Go to latest
Published: Oct 13, 2026 License: MIT Imports: 21 Imported by: 0

Documentation

Overview

Package dogfood answers one question about a tool that its own tests cannot: has somebody who did not write it actually run it?

A tool is not finished until it is tested, dogfooded by a non-author on real work with the edges filed, the feedback applied, documented and released. Nothing tracked that. This package holds the three mechanical halves of the tracking: the list of verbs the command reference declares (cli.go), the receipts a dogfooder writes (receipt.go), and the ledger and the gate that read one against the other (ledger.go). Authorship, which decides whether a receipt counts, is authors.go.

Nothing here reaches the network, and every path is given by the caller.

Index

Constants

View Source
const BareVerb = "-"

BareVerb is how the empty verb — a tool's bare invocation — is written on a line and on the command line, since a field is never empty and a shell argument never should be.

Variables

This section is empty.

Functions

func CmdTools

func CmdTools(cmdDir string) ([]string, error)

CmdTools is the one definition of the tools a checkout ships: every directory under cmd/ whose name starts with nova-, sorted. `release build` compiles exactly this list (release.Tools calls it) and the gate judges exactly this list, so the two cannot disagree about what a release holds.

AN I/O ERROR IS NOT A PARKED TOOL. Each tool directory is read, and a read that fails is returned with the tool's path: a tool directory that cannot be read is a tool nobody can say anything about, and leaving it out of the set would turn its open edges into receipts set aside -- a gate passing because of a permission bit.

func Gate

func Gate(verbs []Verb, receipts []Receipt, authors Authors, requireAll bool) ([]GateFinding, Summary)

Gate is what the release lane calls. It says no on three grounds:

  • an UNMATCHED not-ok receipt: somebody ran something, it did not do what they needed, and the verb they named is not one the list declares. The gate names it, counts it, and includes it in the findings — a receipt that sits in the directory the gate just read but contributed nothing to the verdict would let a release pass on a bench where the only thing anybody found is unreadable. An unmatched receipt that says OK is counted and named and is NOT a failure: a wrong spelling or a stale document is not a reason to stop a release nobody found anything wrong with.
  • an open edge: somebody ran the verb, it did not do what they needed, and nobody has run it since and said it did. Feedback filed is not feedback applied.
  • with requireAll, a verb no non-author has run and passed. This is the definition of done made mechanical: the author's own pass is not evidence the tool works for anybody else.

The unmatched findings come FIRST — a lane reads what was thrown away before it reads anything derived from what was kept — and the rest come back in the reference's order, verb by verb, so the list a release reads is the list the ledger printed.

func GitRunner

func GitRunner(ctx context.Context, dir string, args ...string) (string, error)

GitRunner runs git in a directory, with the context's deadline. It refuses the diff helpers a repository's own config can turn into programs: a core.fsmonitor hook runs on walk-heavy commands (security#56 finding 1), and the textconv and external-diff refusals live in authorArgs beside the commands that would run them (security#77 finding 2).

func IsReceiptID

func IsReceiptID(s string) bool

IsReceiptID reports whether a string is a receipt id, so that the verb which takes one from a caller and the record which stores it ask the same question.

func Ledger

func Ledger(verbs []Verb, receipts []Receipt, authors Authors) ([]Row, Summary)

Ledger reads the receipts against the verbs and returns one row per verb, in the reference's order, and the summary.

One verb can carry several receipts, and the row shows the one that speaks best for the verb: a non-author's pass first, then a non-author's run, then the author's own pass, then whatever there is, latest first inside each rank. The counts do not come from the shown row — `dogfooded` counts verbs with any receipt at all, `by-nonauthor` counts verbs a non-author ran and said ok — so a verb whose newest receipt is an author's still counts as run by the person who ran it.

func Nearest

func Nearest(verbs []Verb, tool, verb string) string

Nearest returns the declared verb closest to what a receipt wrote, or "" when nothing is close enough to be worth suggesting. A verb of the same tool wins over a nearer one of another tool: a remedy that sent a reader to a different binary would be further from the truth than saying nothing.

A verb spelled as a NAME FOR THE BARE FORM -- `(default)`, `bare`, `none`, `(no verb)` -- is answered with the bare form itself, written the way `--verb` takes it: `<tool> --verb -`. Edit distance would send it to whichever short verb happens to be nearest (`(default)` read as `nova-sandbox reap`), and a reader who meant the verbless run would learn nothing about how that run is spelled.

func NormalizeKey

func NormalizeKey(tool, verb string) string

NormalizeKey builds the ledger key from a tool and a verb as somebody wrote them: runs of whitespace collapse, and the bare marker means the bare invocation, so `--verb -` and a reference line with no verb are the same unit.

func PlanRecord

func PlanRecord(dir string, r Receipt) (string, error)

PlanRecord is Record with nothing written: every check Record makes (the receipt, the directory as MkdirAll would make it, the file as the atomic write would check it), the same error, and the path the receipt would take.

func ReadReceipts

func ReadReceipts(dir string) ([]Receipt, []Failure, error)

ReadReceipts reads every receipt in a directory. A file holds one JSON object per line, so a receipts directory written by hand — or appended to by an older tool — reads the same as one Record wrote. Blank lines are skipped; anything else that will not parse, or will not validate, comes back as a failure naming the file and the line, and never as a quietly shorter ledger.

The directory is read shallowly: subdirectories are somebody else's records.

func Record

func Record(dir string, r Receipt) (string, error)

Record appends one receipt to the receipts directory and returns the file it wrote. The append is a new file per receipt, written to a temporary name in the same directory and renamed into place, because two benches recording at once must not interleave halves of two JSON lines into one file — the directory is the append-only log, and each file is one atomic entry.

func RunHelp

func RunHelp(ctx context.Context, bin string) (string, error)

RunHelp is the real one: `<bin> help`, under the context's deadline, stdout and stderr together, because a tool that prints its usage to stderr is still telling you its verbs and the parser ignores every line that is not a command anyway.

func VerbsFromTools

func VerbsFromTools(ctx context.Context, dir string, run HelpRunner, progress Progress) ([]Verb, []Failure, error)

VerbsFromTools asks the binaries themselves what verbs they have.

docs/CLI.md is a maintained reference; a binary's `help` is what the binary does. A discovered help line for a verb the reference has lost is the spelling the binary itself accepts, so the binary's help is authoritative over the reference for verbs the binary actually answers: a receipt against the binary's own spelling would otherwise strand against a reference that has gone stale. When `--tools` names a directory of built binaries, they are the authoritative list and the reference is the fallback for tools that are not in it.

Only files named `nova-*` that are regular and executable are run, each under the caller's deadline. A binary that cannot answer is one named failure, not a dead run: a half-built directory should cost the tools it holds, not the whole ledger.

Types

type Authors

type Authors map[string]string

Authors maps a verb key ("nova-check links") to the person who wrote it. A verb with no entry has no known author, so every receipt for it counts as a non-author's: the gate is about evidence somebody else ran the verb, and an unknown author is not a reason to hold a verb back, it is a reason to write the mapping down.

func AuthorsFromGit

func AuthorsFromGit(ctx context.Context, repo string, verbs []Verb, run Runner, progress Progress) (Authors, error)

AuthorsFromGit works out who wrote each verb by asking the repository. For each verb it takes the FIRST commit that changed the number of times the verb's own word appears under `cmd/<tool>` — the commit that introduced the verb — and reads that commit's author name.

It is the second-best source and says so: the mapping file is exact, this is evidence. A verb git cannot place has no author, and every receipt for it then counts as a non-author's, which is the safe direction: the gate can be wrong by asking for one more pass, never by passing a verb nobody ran.

func ParseAuthors

func ParseAuthors(path string) (Authors, error)

ParseAuthors reads a verb→author mapping. One verb per line:

<tool> <verb> = <author>
<tool> <verb> = <author>
# blank lines and # comments are ignored

The left of the `=` is the verb exactly as docs/CLI.md spells it, tool included; the right is the person who wrote it. A line without an `=`, an empty name, or a verb named twice is refused with the line number, because a mapping that is half wrong makes the gate's answer half wrong in a direction nobody can see.

func (Authors) Author

func (a Authors) Author(key string) string

Author returns the recorded author of a verb, or "" when there is none.

func (Authors) Set

func (a Authors) Set(key, author string)

Set records an author for a verb key.

type Failure

type Failure struct {
	Subject string
	Reason  string
}

Failure is one named NO: the subject that failed and why. A failure means the records themselves are wrong, which is exit 1 at the CLI, never a silently shorter ledger.

type GateFinding

type GateFinding struct {
	Kind   string // "open-edge" or "not-dogfooded"
	Tool   string
	Verb   string
	Reason string
}

GateFinding is one reason the gate says no.

func (GateFinding) Line

func (f GateFinding) Line() string

Line is the finding as the gate prints it.

type HelpRunner

type HelpRunner func(ctx context.Context, bin string) (string, error)

HelpRunner runs one binary's `help` and returns what it printed. It is a parameter so the tests can answer without binaries of their own, and it is the only place this file leaves the process. Nothing here reaches the network.

type MissingFieldError

type MissingFieldError struct {
	Field  string
	Remedy string
}

MissingFieldError names one field a receipt cannot be written without, and the remedy: the flag that supplies it. One field, one line — the CLI prints every one of them in a single run, so a caller who omitted three learns about three.

func (*MissingFieldError) Error

func (e *MissingFieldError) Error() string

type Progress

type Progress func(done, total int)

Progress is told how far along a slow read is. The CLI prints it on stderr once the read has taken longer than a tenth of a second: a program says what it is doing when what it is doing takes long enough to look like a hang.

func NewProgress

func NewProgress(now func() time.Time, after, every time.Duration, print func(done, total int)) Progress

NewProgress returns a Progress that says what a slow read is doing and stays silent on a fast one: nothing at all until the read has been running longer than `after`, then one line at most every `every`, and one at the end.

The clock is a parameter so the policy can be tested against a fake one. Nothing here waits: a progress reporter that made its own tests take a second each would be the slow suite this family refuses.

type Receipt

type Receipt struct {
	Tool  string `json:"tool"`
	Verb  string `json:"verb"`
	By    string `json:"by"`
	At    string `json:"at"` // RFC3339, UTC
	OK    bool   `json:"ok"` // did the verb do what the run needed
	Notes string `json:"notes"`
	Issue int    `json:"issue,omitempty"` // the edge filed, when there is one

	// Closes is the ID of the finding this run answers, when it answers one.
	//
	// A finding is closed only by a receipt that names it or by the person who
	// found it running the verb again. Any other later run leaves it open: a
	// run that happened to find nothing must not put the finding out of the
	// gate's sight, where nobody reads the note, nobody files the issue, and
	// the ledger row shows ok=yes over it.
	Closes string `json:"closes,omitempty"`

	// File is where this receipt was read from. It is not part of the record —
	// a receipt does not know its own path — and it is here so a failure can
	// name the receipt it came from: a reader told that receipts matched
	// nothing learns which ones without working it out.
	File string `json:"-"`
}

Receipt is one person saying: I ran this verb, on this day, on real work, and here is how it went. It is the whole record — there is no separate state, and nothing here is derived, because a dogfood claim nobody wrote down is a dogfood claim nobody can check.

func (Receipt) Filed

func (r Receipt) Filed() bool

Filed reports whether the edge this receipt records has an issue somebody can act on.

func (Receipt) ID

func (r Receipt) ID() string

ID is this receipt's name, short enough for a person to type into a --closes.

It is a fact of the CONTENT, not of the file: the same receipt read from two places is one receipt with one id, and a receipts directory holds no counter anybody has to keep. It is the same eight characters Record puts at the end of the filename, so a reader with an id off the gate's line can find the file it came from.

func (Receipt) Key

func (r Receipt) Key() string

Key is the verb this receipt is about, in the ledger's identity. `--verb -` is the tool's bare invocation and normalizes to the empty verb, so a receipt can name a tool that has no verbs at all.

func (Receipt) RecordLine

func (r Receipt) RecordLine(path string) string

RecordLine is what `record` prints once the receipt is on disk: the record as the ledger will read it, and the file it was written to.

func (Receipt) RecordsAnEdge

func (r Receipt) RecordsAnEdge() bool

RecordsAnEdge reports whether this receipt found something: the verb did not do what the run needed, or the notes name an edge in the shape the family writes them. The prose is not read any further than that marker — what the edge IS stays a human's to read.

func (Receipt) Spelling

func (r Receipt) Spelling() string

Spelling is the verb as a line prints it, with the bare marker for a tool that has no verbs.

func (Receipt) Time

func (r Receipt) Time() time.Time

Time parses At. A receipt that has been through Validate always parses.

func (Receipt) Validate

func (r Receipt) Validate() []error

Validate reports every field a receipt is missing or cannot hold, in a fixed order. A receipt is the evidence; evidence with a blank in it is not evidence, so nothing is defaulted here — not the clock, not the name.

type Row

type Row struct {
	Tool      string
	Verb      string
	By        string // the dogfooder's name, or "nobody"
	At        string // RFC3339, or "-"
	OK        string // "yes", "no", or "-" when nobody has run it
	Issue     string // the edge filed, or "-"
	NonAuthor bool   // the receipt shown is somebody other than the author's

	// Open is how many findings on this verb nobody has answered.
	//
	// It is on the row because the row is the line that HID one (round 5, edge
	// 2): the row shows the receipt that speaks best for the verb, so a second
	// dogfooder's ok=yes was printed over a first dogfooder's open finding, and
	// the one line a reader parses claimed a clean verb. It is printed whether
	// it is zero or not — a field that appears only when it is interesting is a
	// field nobody can parse.
	Open int
}

Row is one verb's line in the ledger: the verb, and the receipt that speaks best for it. Every field is already the token the CLI prints, so the line grammar lives in one place and the tests can read it.

func (Row) Line

func (r Row) Line() string

Line is the row as the ledger prints it. A tool with no verbs prints `verb=-`: its bare invocation is a unit like any other, and a unit with no row can never be dogfooded.

type Runner

type Runner func(ctx context.Context, dir string, args ...string) (string, error)

Runner runs one command and returns its standard output. It is the only way this package reaches outside the process, and it is a parameter so the tests can answer without a git of their own. Nothing here reaches the network.

type Shipped

type Shipped struct {
	Dir string
	// contains filtered or unexported fields
}

Shipped is the set of tools a release ships: the nova-* directories under cmd/ at the release commit, which is the list `release build` compiles. A tool not under cmd/ is not built, not tested and not shipped, so no receipt about it can speak for or against the release.

THE GATE JUDGES WHAT SHIPS. A receipt names a tool, and a tool that is not in the set is a tool this release does not contain: its open edges and its not-ok runs are true about that tool and say nothing about the sixteen that ship. Counting them made the 1.0.0 cut refuse on 34 findings against tools the release does not hold, which is a gate nobody can pass and therefore a gate somebody waives.

func NewShipped

func NewShipped(tools ...string) Shipped

NewShipped is a shipped set named directly, for callers that already know it.

func ReadShipped

func ReadShipped(cmdDir string) (Shipped, error)

ReadShipped reads the shipped set from a cmd/ directory, by CmdTools. A directory that cannot be read, a tool directory under it that cannot be read, or a cmd/ that holds no tool is refused: a scope that came back short would set receipts aside that it has no grounds to.

func (Shipped) Has

func (s Shipped) Has(tool string) bool

Has reports whether the release ships this tool.

func (Shipped) Scope

func (s Shipped) Scope(verbs []Verb, receipts []Receipt) ([]Verb, []Receipt, []Receipt)

Scope keeps the verbs and receipts of shipped tools and returns the receipts it set aside, so the caller can COUNT them on its line: evidence left out of the arithmetic is named, never dropped in silence.

func (Shipped) Tools

func (s Shipped) Tools() []string

Tools is the set, sorted.

type Strand

type Strand struct {
	Receipt Receipt
	File    string // where the stranded receipt lives
	Nearest string // the closest declared verb, or "" when nothing is close
}

Strand is one receipt that named a verb the reference does not declare: the file it is in, what it said, and the declared verb it was probably meant to be. Each strand is named so a reader can find the receipt, read what it recorded, and pick the declared verb that matches; a receipt that matched nothing would otherwise leave the summary's arithmetic and the gate's verdict untouched, and nobody could tell which run went where.

func Stranded

func Stranded(verbs []Verb, receipts []Receipt) []Strand

Stranded returns every receipt naming a verb the reference does not declare, in the order they were read, each with the nearest declared verb.

func (Strand) Line

func (s Strand) Line() string

Line is the strand as the ledger and the gate print it.

type Summary

type Summary struct {
	Verbs       int // verbs docs/CLI.md declares
	Dogfooded   int // verbs with at least one receipt
	ByNonAuthor int // verbs a non-author has run and said ok
	OpenEdges   int // edges nobody has since cleared
	Unfiled     int // of those, the ones with no issue anybody can act on

	// Unmatched is the receipts naming a verb the list does not declare. It is
	// a COUNT ON THE LINE, not a note beside it: a receipt that matches
	// nothing falls out of every count, so the gate can report open-edges=0
	// at exit 0 with not-ok receipts in the directory it just read. A count
	// that silently leaves evidence out is worse than no count, so this one
	// is printed on the line whether it is zero or not.
	Unmatched int
}

Summary is the count line under the rows.

func (Summary) GateCountLine

func (s Summary) GateCountLine(findings, shown int) string

GateCountLine is the gate's red count line: the total, and how much of it was printed under the ceiling.

func (Summary) GateLine

func (s Summary) GateLine(requireAll bool) string

GateLine is the gate's green line: what it checked and what it found.

func (Summary) Line

func (s Summary) Line() string

Line is the summary as the ledger prints it.

type Verb

type Verb struct {
	Tool string
	Verb string
	Line int
}

Verb is one unit of dogfooding: the tool that owns it, the verb itself, and the line of the reference that declared it first. A tool with no verbs — one run as `nova-decide --questions <file>` — carries the empty verb, which is its bare invocation and prints as `verb=-`.

func MergeVerbs

func MergeVerbs(authoritative, fallback []Verb) []Verb

MergeVerbs puts the authoritative list first and lets the fallback fill in only the tools the first one says nothing about. A tool that answered for itself is complete by definition — a verb of its in the reference and not in its help is a stale reference, not a missing verb.

func ParseCLI

func ParseCLI(path string) ([]Verb, error)

ParseCLI reads a command reference (docs/CLI.md) and returns every unit it declares, in the order the document declares them, deduplicated on tool+verb.

The rule is mechanical, because a rule a reader cannot apply by hand is a rule nobody can check the tool against. It reads the three shapes this reference actually uses. A tool documented as prose with a worked transcript, or by pasting its own indented help block, would otherwise go un-dogfooded forever: the extractor reads no rows for it, not because nobody runs it but because it is documented in a shape the extractor does not read. So the extractor reads every shape:

  • **a command line inside a fenced block**, at any indentation, with or without a `$ ` prompt and with or without leading `VAR=value` environment prefixes. The first token is the tool, `nova-<something>`; the verb is the leading lowercase bare words after it, at most two, so `nova-fuse lift quarantine` and `nova-fuse lift lockdown` are the two verbs they are while `nova-check links --dir <dir>` stops at the first flag. A line whose next token is a flag, a placeholder or a bracket — `nova-decide --questions <json file>` — declares the tool's BARE invocation, which is a unit like any other: it is how that tool is run.
  • **a `### <verb>` heading** under a `## nova-<tool>` section, which is how a verb documented in prose declares itself. One word only, and the heading must be that word alone or that word before a separator, so `### cut` and `### serve: the process outside a session` are verbs and `### native and batch` and `### The seven verbs` are prose.

The tool comes from the line and not from the section heading: a reference shows one tool's verb inside another tool's section, and a verb belongs to the tool that runs it. Headings are the exception — a heading has no tool in it, so it takes the section's.

func ParseHelp

func ParseHelp(text string) []Verb

ParseHelp is the same rules over a binary's own `help` output, which is a usage block and nothing else: there are no fences to be inside of, so every line is read as a command line.

func ParseReference

func ParseReference(text string) []Verb

ParseReference is ParseCLI over text already in hand: a markdown reference, where only fenced blocks and `###` headings declare.

func (Verb) Key

func (v Verb) Key() string

Key is the ledger's identity for a verb: what a receipt has to name to be about this verb.

func (Verb) Spelling

func (v Verb) Spelling() string

Spelling is the verb as a row prints it and as `--verb` takes it.

Jump to

Keyboard shortcuts

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