converge

package
v1.2.13 Latest Latest
Warning

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

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

Documentation

Overview

Package converge answers one question mechanically: are we converging?

Convergence is the health metric — the contraction ratio per stream, every tick. It is computed here, mechanically, so one run's answer can be diffed against the next. A stream here is one number a converging family drives in one direction, read now and read at --since, with the ratio between them.

Everything in this file is PURE: it takes already-fetched data and returns findings. The forge, git, the filesystem and the clock are seams (forge.go, git.go, sources.go), so every test is fake-driven and nothing here reaches a network. See docs/SPEC-CHECK.md.

Index

Constants

View Source
const BatchPrefix = "integration-"

BatchPrefix is what makes a merged pull request a batch: integration batches only — nothing else enters the queue.

View Source
const ForgeLimit = 1000

ForgeLimit is the most pull requests one read will take. A read that hits it is REFUSED rather than truncated: a convergence number computed from the first thousand of an unknown number is a measurement with a lie in it.

View Source
const SpecCIPath = "docs/SPEC-CI.md"

SpecCIPath is the document CLASSES counts, relative to the checkout root.

Variables

View Source
var Order = []string{"LANDING", "CLASSES", "SCRIPTS", "PRS", "EDGES", "FLEET", "LEDGER"}

Order is the fixed order of the reading.

Functions

func ClassTests

func ClassTests(spec string) []string

ClassTests returns the `###` entries under `## The class tests`, in the order the document prints them. Entries outside that section are not counted — the document is full of `###` headings that are not class tests.

func CountScripts

func CountScripts(dir string) (int, error)

CountScripts counts the scripts left in a directory: a regular file whose name carries a script extension, or whose first two bytes are `#!`. Subdirectories are NOT walked — `bin/retired/` is the other side of this measure, and counting it would make the finish line unreachable.

func LedgerRows

func LedgerRows(md string) (rows, open int)

LedgerRows counts the ledger's table rows and the ones not yet closed. A cell is closed when it holds PASS and none of the open words, so `FAIL then PASS` is closed and `FAIL` alone is not.

func Num

func Num(v float64, have bool) string

Num renders one measured number: an integer when it is one, two decimals when it is not, and `-` when the stream does not have it.

func ParseCerts

func ParseCerts(path, text string) (certified, total int, err error)

ParseCerts reads the certificate roll-up and returns the certified units and the total. `certified` is the one status that counts; every other word is a unit that is not.

func ParseSince

func ParseSince(s string, now time.Time) (time.Time, error)

ParseSince reads the far edge of the window in either spelling the flag takes: an RFC3339 instant, or a Go duration meaning that long before now. A window that ends before it starts is refused here rather than printed as a negative rate.

func PlanSave

func PlanSave(path string) error

Save writes the state file, whole, through a temporary file in the same directory: a tick killed halfway through leaves the previous tick's memory rather than half of this one's. PlanSave is Save with nothing written: every check the write makes, and the same error.

func RetiredInWindow

func RetiredInWindow(rows []RetiredRow, since, until time.Time) (inWindow, undated int)

RetiredInWindow counts the rows dated inside [since, until], and the rows nobody dated. Both numbers are printed: an undated row is not this window's, and a reader who is not told how many there are will read the count as whole.

func RoundsFromBody

func RoundsFromBody(body string) (int, bool)

RoundsFromBody reads the rounds a batch's body names.

func RoundsFromLogs

func RoundsFromLogs(dir string) (map[int]int, error)

RoundsFromLogs reads the highest round each pull request has a log for.

Types

type Batch

type Batch struct {
	Number     int
	MergedAt   time.Time
	Rounds     int
	HaveRounds bool
}

Batch is one integration batch that landed: the pull request, when it merged, and how many gate rounds it took.

func Batches

func Batches(merged []PR, logs map[int]int) []Batch

Batches turns merged pull requests into batches, taking each one's rounds from the logs when the logs know it and from the body otherwise. The logs win: a body is written by hand, and the hand is the thing this verb is replacing.

type Field

type Field struct {
	Key   string
	Value string
}

Field is one key=value extra on a stream's line, after the four the spec fixes. The key is written by this package; the value goes through oneline.

type Forge

type Forge interface {
	// OpenPRs is every pull request open right now.
	OpenPRs(ctx context.Context) ([]PR, error)
	// ClosedSince is every pull request closed or merged at or after `since`.
	ClosedSince(ctx context.Context, since time.Time) ([]PR, error)
}

Forge is what this verb needs from a forge, and all of it. Neither method writes: convergence reads the queue, it never touches it.

type GH

type GH struct {
	Repo    string
	Timeout time.Duration
	// Bin is the gh executable, "gh" unless a caller names another. A test
	// names a fake; nothing here searches a path of its own.
	Bin string
}

GH is the real forge: `gh pr list`, bounded, reading only. GH_CONFIG_DIR and the rest of the environment come from the caller's, because which identity gh speaks as is the caller's business and never this verb's.

func (GH) ClosedSince

func (g GH) ClosedSince(ctx context.Context, since time.Time) ([]PR, error)

ClosedSince lists what closed or merged inside the window. The search is by DAY, because that is the resolution the forge's search takes; the window's own edges are applied afterwards by the streams, on the timestamps.

func (GH) OpenPRs

func (g GH) OpenPRs(ctx context.Context) ([]PR, error)

OpenPRs lists what is open now.

type Git

type Git interface {
	// RevBefore is the last commit on the current branch at or before t.
	RevBefore(ctx context.Context, t time.Time) (string, error)
	// Show is one path's content at one revision.
	Show(ctx context.Context, rev, path string) (string, error)
}

Git is what CLASSES needs from a checkout, and all of it.

type JSON

type JSON struct {
	At          string       `json:"at"`
	Since       string       `json:"since"`
	Streams     []StreamJSON `json:"streams"`
	Verdict     string       `json:"verdict"`
	Measured    int          `json:"streams_measured"`
	Contracting int          `json:"contracting"`
	Widening    []string     `json:"widening"`
	Absent      []string     `json:"absent"`
}

JSON is the whole reading as one object: the same numbers the lines print, for a reader that is a program.

type Options

type Options struct {
	Repo        string
	LedgerPath  string
	ReceiptsDir string
	RetiredPath string

	BinDir       string // SCRIPTS
	RepoDir      string // CLASSES
	BatchLogs    string // LANDING's second source for rounds
	VersionsPath string // FLEET
	CertsPath    string // FLEET's certified fraction

	By []string

	Since time.Time
	Now   time.Time

	Forge Forge
	Git   Git // nil when --repo-dir was not given
}

Options is every source of one reading. A path that is empty is a source the caller did not name, and its stream is ABSENT -- never zero, and never guessed from the working directory.

type PR

type PR struct {
	Number    int
	Title     string
	Body      string
	Merged    bool
	CreatedAt time.Time
	ClosedAt  time.Time
}

PR is one pull request, in the four facts convergence reads.

func Merged

func Merged(prs []PR) []PR

Merged keeps the pull requests that landed, which is what a batch is.

type RealGit

type RealGit struct {
	Dir     string
	Timeout time.Duration
	// Bin is the git executable, "git" unless a caller names another.
	Bin string
}

RealGit runs git in one directory, bounded.

func (RealGit) RevBefore

func (g RealGit) RevBefore(ctx context.Context, t time.Time) (string, error)

RevBefore asks for the last commit at or before an instant.

func (RealGit) Show

func (g RealGit) Show(ctx context.Context, rev, path string) (string, error)

Show reads one path at one revision.

type Report

type Report struct {
	Streams []Stream
}

Report is one tick: every stream in the fixed order the spec prints them.

func Read

func Read(ctx context.Context, o Options) (Report, error)

Read takes the reading. Every refusal it returns is exit 2 at the command line and prints no stream line: a partial reading of convergence is the thing this verb exists to replace.

func (Report) Apply

func (r Report) Apply(st State, now time.Time) (Report, State, bool)

Apply folds the remembered tick into this one and returns the report with its `before` filled in where only history could supply it, the new state, and whether any stream has now widened on two consecutive ticks -- the one condition that is exit 1.

It is one function because the two halves are one decision: a stream's `before` and its widening streak are read from the same remembered entry, and a version of this that filled one without updating the other is how a streak gets counted twice.

func (Report) AsJSON

func (r Report) AsJSON(at, since time.Time) JSON

AsJSON renders the report.

func (Report) Lines

func (r Report) Lines() []string

Lines is the whole reading: one line per stream, then the verdict.

func (Report) Verdict

func (r Report) Verdict() (measured, contracting int, widening, absent []string)

Verdict is the closing line's arithmetic: how many streams were measured, how many are contracting, which are widening and which were never read.

func (Report) VerdictLine

func (r Report) VerdictLine() string

VerdictLine is the last line of the reading.

type RetiredRow

type RetiredRow struct {
	Script   string
	Date     time.Time
	HaveDate bool
}

RetiredRow is one script that was retired, and the day the document says so.

func ParseRetired

func ParseRetired(md string) []RetiredRow

ParseRetired reads the retired README: every table row is one retired script, dated by the nearest dated heading or paragraph ABOVE it. A row under no date at all is returned with HaveDate false rather than dropped — a row nobody dated is evidence, and counting it as this window's would be the guess.

type State

type State struct {
	Streams map[string]StreamState `json:"streams"`
}

State is the whole memory of the verb: one entry per stream. There is no other state anywhere, and without --state there is none at all.

func LoadState

func LoadState(path string) (State, error)

LoadState reads the state file. A file that is not there is an empty state and not an error: the first tick of a new stream has nothing to remember.

func (State) Save

func (s State) Save(path string) error

type Stream

type Stream struct {
	Name    string // LANDING, CLASSES, SCRIPTS, PRS, EDGES, FLEET, LEDGER
	Measure string // what the number counts, printed as measure=

	Now    float64
	Before float64

	HaveNow    bool
	HaveBefore bool

	// Lower is true when fewer is converging. CLASSES is the one stream where
	// it is false: a class made mechanical cannot come back, so more entries is
	// the family contracting, not widening.
	Lower bool

	// Want is the flag that would have fed this stream, on an absent one.
	Want string

	// BeforeFrom names where `before` came from when it was not read from a
	// source of its own: `state` for the two streams a single photograph cannot
	// give a history.
	BeforeFrom string

	Extra []Field
}

Stream is one measure of convergence: what it counts, what it counts now, what it counted at --since, and which way it is meant to move.

func AbsentStream

func AbsentStream(name, measure, want string, lower bool) Stream

AbsentStream is a stream nobody gave a source for: named, counted on the verdict line, and never guessed at.

func Classes

func Classes(nowSpec string, beforeSpec string, haveBefore bool) Stream

Classes counts the class-test index at both revisions. It is the one stream that converges upwards.

func Edges

func Edges(receipts []dogfood.Receipt, since, now time.Time, by []string) Stream

Edges is the dogfood gate: the edges nobody has filed an issue for, now and at --since, with the not-ok rate of the rounds inside the window beside it. `by` narrows the rounds to the named friends; empty reads them all.

func Fleet

func Fleet(rows []VersionRow, certified, total int, haveCerts bool) Stream

Fleet is one build across the machines: the units NOT on the majority stamp, so a fleet on one build reads zero. Its `before` can only come from the state file — a snapshot is a photograph of one instant, and there is no honest way to ask it what yesterday looked like.

func Landing

func Landing(batches []Batch, since, now time.Time) Stream

Landing is the cost of landing: gate rounds per integration batch in the window, against the window of equal length immediately before --since.

func Ledger

func Ledger(md string) Stream

Ledger is what the pit stop still owes: the rows not yet closed. Like FLEET, its `before` comes from the state file, because the ledger's rows carry a status and not a date.

func PRs

func PRs(open, closed []PR, since, now time.Time) Stream

PRs is the queue at both ends: open now, and open at --since. The second is arithmetic rather than a query, because a forge answers what is open now and nothing answers what was open then: it is the still-open ones created before --since, plus the ones created before it and closed inside the window.

func Scripts

func Scripts(remaining int, rows []RetiredRow, since, now time.Time) Stream

Scripts is the sprint's finish line: what is left in bin, against what was left at --since — which is what is left now plus what the window retired.

func (Stream) Absent

func (s Stream) Absent() bool

Absent reports whether this stream was read at all.

func (Stream) Line

func (s Stream) Line() string

Line is the stream's whole output: the four fields the spec fixes, then this stream's own extras.

func (Stream) Ratio

func (s Stream) Ratio() (float64, bool)

Ratio is always now/before, whichever way the stream converges, so one column means one thing down the whole reading. A before of zero has no ratio: the line prints `-` rather than an infinity dressed up as a measurement.

func (Stream) Trend

func (s Stream) Trend() Trend

Trend is the direction in the stream's own terms.

func (Stream) With

func (s Stream) With(key, value string) Stream

With adds one extra field, in the order the caller adds them.

func (Stream) WithInt

func (s Stream) WithInt(key string, n int) Stream

WithInt adds one counted extra field.

func (Stream) WithNum

func (s Stream) WithNum(key string, v float64, have bool) Stream

WithNum adds one measured extra field, rendered like the four fixed ones.

type StreamJSON

type StreamJSON struct {
	Name    string            `json:"stream"`
	Measure string            `json:"measure"`
	Now     *float64          `json:"now"`
	Before  *float64          `json:"before"`
	Ratio   *float64          `json:"ratio"`
	Trend   string            `json:"trend"`
	Want    string            `json:"source,omitempty"`
	Extra   map[string]string `json:"extra,omitempty"`
}

StreamJSON is one stream in that object. A number the verb does not have is null rather than zero, for the same reason the line prints `-`.

type StreamState

type StreamState struct {
	Now      float64 `json:"now"`
	At       string  `json:"at"`
	Widening int     `json:"widening_ticks"`
}

StreamState is what one tick remembers about one stream: the value it read, when it read it, and how many consecutive ticks that stream has been widening. The streak is the only thing in this verb that cannot be recomputed from the sources, which is why it is the only thing kept.

type Trend

type Trend string

Trend is which way a stream moved, in the stream's own direction of travel.

const (
	// Contracting is the number moving the way this stream converges.
	Contracting Trend = "contracting"
	// Flat is a number that did not move, and a first tick, which has nothing
	// to have moved from.
	Flat Trend = "flat"
	// Widening is the number moving the other way: the one word this verb
	// exists to print.
	Widening Trend = "widening"
	// AbsentTrend is a stream whose source was not named. It is not zero and it
	// is not flat — it is unread, and the line says so rather than contributing
	// a number nobody measured.
	AbsentTrend Trend = "absent"
)

type VersionRow

type VersionRow struct {
	Unit  string
	Stamp string
}

VersionRow is one unit of the fleet and the build it is on. A unit is a machine in a roll-up and a binary in one machine's snapshot; the measure is the same either way — how many are not on the one build.

func ParseVersions

func ParseVersions(path, text string) ([]VersionRow, error)

ParseVersions reads either header and refuses anything else by name. A file this verb half-understands is worse than one it refuses: the number it would print is a fleet on one build.

Jump to

Keyboard shortcuts

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