release

package
v1.2.11 Latest Latest
Warning

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

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

Documentation

Overview

Package release is the last mile: a green commit on main becomes a version, a set of binaries, and the same binaries answering for themselves on every bench in the fleet.

It exists because that mile was a shell script. `fleet-install-tools.sh` built the tools, cached them by sha, tarred them to each bench and installed them by rename -- 30 lines of nested ssh quoting with a build, a cache, a copy, an install and a verify all in one `for` loop, and no test of any of it. It was written after a pit stop where benches ran six-hour-old tools while the coordinator believed they were current, and the reason that could happen is that nothing in the loop could SAY what it had done: the install printed one line per bench and the line was composed from the same ssh it was reporting on.

So the four steps are four verbs, each of which can refuse:

cut      main is green, here is the version, here is what changed
build    every cmd/nova-* for one platform, stamped, with a checksum file
install  verify the checksums, put them in place atomically, skip what is current
adopt    do the install on every machine in a file, one receipt line each

The three edges to the world outside this process -- the forge, ssh, and the Go toolchain -- are interfaces, so that a test can watch every argument this tool would hand a subprocess and so that the production implementations are short enough to read line by line. No unit test here reaches the network or a real machine.

NOTHING HERE TOUCHES A SECRET. gh carries its own credential, ssh carries its own key, and neither is read, logged or passed by this package.

Index

Constants

View Source
const (
	DogfoodWaiveFlag  = "--no-dogfood-gate"
	DogfoodReasonFlag = "--reason"
)

DogfoodWaiveFlag and DogfoodReasonFlag are the way past the gate, spelled in one place so the remedy a person is handed is the flag they then type.

View Source
const (
	JourneysProvenPrefix     = "Recovery journeys proven at "
	JourneysIncompletePrefix = "Recovery journeys incomplete, gate waived: "
)

JourneysProvenPrefix and JourneysIncompletePrefix are how the CHANGELOG section names what the gate found.

View Source
const (
	OpenRouterActivityURL = "https://openrouter.ai/api/v1/activity"
	OpenRouterActivityEnv = "OPENROUTER_PROVISIONING_KEY"
	// OpenRouterActivityDays is how far back the activity reaches.
	OpenRouterActivityDays = 30
)

OpenRouter's readouts: the account's activity (GET with a provisioning key, the usage of each of the last completed UTC days, by model), and the key's own count of today (GET /key, data.usage_daily), the day the activity does not hold yet.

View Source
const AdoptNote = "adopt runs FROM the host that has ssh to every machine and fans out from there; it never needs the machines to reach each other. " +
	"When the release was built elsewhere, --from may name that machine as host:dir and --stage <dir> says where to fetch it first. " +
	"Such a fetch is verified against a digest that did NOT travel with the bits: --repo <owner/name> reads it off the annotated tag the cut wrote, --expect-sums <sha256> names it outright, or --expect-sums-from <file> reads it out of the " + DigestFile + " this host's own `release build` wrote. " +
	"A dev build has no tag, which is why the third exists; the file must be a LOCAL one, because a digest computed on the machine holding the bits is that machine vouching for itself. " +
	"Install the release on this host before adopting it: the nova-update running the fan-out is the one here, and a coordinator older than the release it is adopting refuses and says so. " +
	"--machines is " + MachinesShape + ". " + RemotePathsNote + ". " +
	"--retire <dir> removes this release's own nova-* files from a second directory nobody should still be running from (~/go/bin); it refuses to be --bin or the live stamp. " +
	"--bin, --dest and --retire must be absolute or ~/-rooted and free of shell metacharacters; they are validated before any remote command is composed."

AdoptNote is what a person needs before their first adopt, and every sentence of it is something the first dogfood pass had to find out by failing.

View Source
const AnnotationSumsPrefix = "sums="

AnnotationSumsPrefix is how the TAG names the same digest, and it is written on a line of its own so that reading it back is an anchored match rather than a search through prose.

View Source
const CIWaiverChecksPrefix = "CI waived checks: "

CIWaiverChecksPrefix opens the line that names the checks the waiver covered.

View Source
const CIWaiverPrefix = "CI waived: "

CIWaiverPrefix is how the tag annotation and the CHANGELOG section name a waived CI gate, spelled once so the two records cannot drift. A red CI is let past only by the cut's own --waive-ci, and what it let past outlives the terminal: a person asking in six months why a version shipped on a red commit reads the answer in the tag. docs/SPEC-RELEASE.md section 19.

View Source
const CompareFileCap = 300

CompareFileCap is how many files the forge will name for one compare. It is GitHub's own ceiling on the `files` array of a compare response, and it matters here for one reason: A FILE LIST AT THE CEILING IS A LIST THAT MAY BE SHORT. The classification below reads that list, so a range at this number cannot be classified at all, and a gate that reads a truncated list is a gate that passes the one file it did not see. `cut` refuses such a range with the same remedy as a sensitive one rather than quietly deciding on a prefix of the truth.

View Source
const CycleNote = "cycle is the fix-land-install cycle from the coordinator in one command: the tools play (<--source>/fleet/tools.yml) with --check, then the play itself, limited to --benches, localhost and the store_deployer group, and the build's schema and function library on the store runs on every cycle. " +
	"The play builds every missing platform with `release build --incremental --gate report --reason <why>`, copies only the binaries a bench does not already hold, and installs; " +
	"one CYCLE BENCH line per bench names the version it now runs, and both plays' output is kept under <--out>/<version>/cycle-check.log and cycle-apply.log. " +
	"--dry-run runs the check alone. It needs the inventory's environment (the store, the seat) exactly as the play does. A release cut keeps the refusing gate: cycle is for a machinery install during a sprint."

CycleNote is the verb said where a person meets it.

View Source
const DigestFile = "SUMS.digest"

DigestFile holds the sha256 OF SumsFile, written beside it by `release build`.

`adopt` fetching a release from another machine must check it against a digest that did NOT travel with the bits, and the two ways to have one -- the annotated tag and the CHANGELOG entry -- both belong to a TAGGED release. A dev build has no tag, so the only other place to get a digest is the machine being adopted FROM, which is that machine vouching for its own bytes and is not evidence at all. This file is written where the build ran, on the coordinator, out of the SHA256SUMS the build had just verified; `adopt --expect-sums-from` reads it from there. A digest computed on the machine being adopted from is not evidence about a fetch.

View Source
const DogfoodRemedy = "fix the open edges or " + DogfoodWaiveFlag + " " + DogfoodReasonFlag + " <why>"

DogfoodRemedy is what the refusal tells somebody to do about it. Two ways out and both are work: fix the edges, or waive the gate and say why where the waiver will outlive the terminal.

View Source
const DogfoodWaiverPrefix = "Dogfood gate waived: "

DogfoodWaiverPrefix is how the CHANGELOG section names a waived gate. The waiver travels with the release, in the file a person reads to find out what a version is, because a waiver that lives only in one terminal's scrollback is a waiver nobody can weigh in six months.

View Source
const EvidenceKind = "release-journeys"

EvidenceKind is what the evidence header's "evidence" field says, so a file of some other JSON is refused rather than read as an empty run.

View Source
const ExitCodes = "exit codes: 0 the verb did what its line says (a --dry-run printed its plan and changed nothing); " +
	"1 it ran and a step failed partway, the FAILED or REFUSED line naming what was done and what to do next; " +
	"2 it refused before acting, naming the command to run."

ExitCodes is the release verbs' exit-code line, which each verb's -h prints.

View Source
const IncrementalNote = "build writes a record beside each platform directory, <out>/<version>/<goos-goarch>" + RecordSuffix + ": the commit (none when the checkout is dirty), the Go, the stamp shape, the gate and its reason. " +
	"--incremental compiles only the tools whose packages, or the packages they import, differ (`git diff --name-only --no-renames`) between the newest such record under --out and the checkout, and copies every other tool from that build, verified against its SHA256SUMS; " +
	"a reused tool answers the version it was built at. A dirty checkout, another Go, a go.mod or go.sum change, or no record is a whole build, said on RELEASE BUILD WHOLE; otherwise RELEASE BUILD INCREMENTAL names the base and every tool rebuilt. " +
	"--gate report --reason <why> runs the dogfood gate, prints its open edges and RELEASE BUILD DOGFOOD REPORTED, and builds (dogfood=report); it is for a machinery install during a sprint, and cut has no such flag."

IncrementalNote is --incremental and --gate report said where a person meets them.

View Source
const JourneyRemedy = "run each promised journey at the release revision and pass --journeys <file>, or " + JourneyWaiveFlag + " " + DogfoodReasonFlag + " <why>"

JourneyRemedy is what the refusal tells somebody to do.

View Source
const JourneyWaiveFlag = "--no-journey-gate"

JourneyWaiveFlag is the way past the gate, spelled once.

View Source
const KeepBesides = 3

KeepBesides is how many release versions a root keeps BESIDES the ones it must: the version just built or installed, and the version the machine was running before it. A root that keeps every version forever, one directory per dev build, runs a machine out of disk; the measurement below is that root, not a claim every machine reaches it. Measurement (2026-10-01): 36 versions, 9 GB on one root; 34 GB on another.

Three is enough to put back any of the last few builds by re-installing it without a rebuild, which is the only thing an old version directory is for, and it bounds a root at five versions whatever the day's build rate.

View Source
const MachinesShape = "" /* 169-byte string literal not displayed */

MachinesShape is the one sentence that says what the --machines file holds. The help prints it and docs/SPEC-UPDATE.md carries it, because a file format discoverable only from a refusal is a format nobody can write correctly the first time, so the shape is stated here rather than learned from the source.

View Source
const PathsHeaderPrefix = "# nova-update release cut --local-diff "

PathsHeaderPrefix is the first line of a path list `release cut --local-diff` wrote, and the whole reason --paths-from can be trusted: the rest of the file is a classification gate's INPUT, and a gate reading a hand-written input is a gate whose answer is whatever somebody remembered. The line also names the RANGE, so a list left over from a different pair of commits is refused rather than quietly classifying a release that is not this one.

View Source
const PlatformUnavailable = "PLATFORM UNAVAILABLE "

PlatformUnavailable is how a journey's skip says its platform was not there to run on: `t.Skip(release.PlatformUnavailable + "windows: no windows bench answered")`. It counts only for a platform the journey names as Optional.

View Source
const PullNote = "pull withdraws a release that should not have shipped (a leak, a key, a file that was never meant to travel). " +
	"THE TAG STAYS: a tag that vanishes is a history that cannot be read, so the CHANGELOG section is marked " + PulledPrefix + "<date>** instead, carrying --reason. " +
	"What is deleted is the ARTIFACTS: the release's own files under --out here, and the same files under --dest on every machine in --machines, by name, never recursively. " +
	"The names come from that release's own " + SumsFile + " under --out, so --out must still hold the release being pulled. " +
	"It does not touch an INSTALLED binary: a machine keeps running what it is running until the next release is adopted over it."

PullNote is what a person needs before their first pull, in the help, because every sentence of it is a decision somebody would otherwise have to guess at.

View Source
const PulledPrefix = "**PULLED "

PulledPrefix is how a changelog section says the release was withdrawn. It is a prefix rather than a whole sentence because the note carries a date and a reason, and it is one constant because `pull` writes it and `pull` reads it back: a second run must not stack a second note.

View Source
const ReceiptsKind = "spend-receipts"

ReceiptsKind is what a receipts file's "evidence" field says.

View Source
const RecordSuffix = ".build"

RecordSuffix names the build record written beside each platform directory, <out>/<version>/<goos-goarch>.build. It sits OUTSIDE the platform directory so that it is never in SHA256SUMS, never copied to a bench and never installed: it is this host's note about how the artifacts were made.

View Source
const RemotePathsNote = "--bin and --dest are paths on each machine; the remote shell expands a leading ~, so quote it ('~/.local/bin') or the local shell expands it here instead. " +
	`A windows target takes the drive form too ('C:\Users\nova\.local\bin'), folded to forward slashes before any command is composed -- the far side's ssh shell is Git Bash (docs/BENCH-WINDOWS.md) and a backslash there is an escape. The drive form is refused for every other target`

RemotePathsNote says the one thing about --bin and --dest that is easy to get wrong and silent when you do. They are paths ON THE MACHINE: the remote shell expands a leading ~, so `--bin '~/.local/bin'` is how three different home directories are named at once -- and the quotes are load-bearing, because an unquoted ~ is expanded by the LOCAL shell into the adopting host's home, which is a path the machine has probably never heard of.

And the windows bench's own form is said here rather than found out at a refusal: it is what docs/BENCH-WINDOWS.md puts in that bench's runner .path, so it is what a person will type.

View Source
const SpendGapOver = 0.05

SpendGapOver is the share of the provider's own figure a gap must pass to refuse.

View Source
const SpendRemedy = "find the spend the store did not record (nova-sprint cost reconcile; card <id> shows the records), give every provider a readout and the friends their receipts, or " +
	SpendWaiveFlag + " " + DogfoodReasonFlag + " <why>"

SpendRemedy is what the refusal tells somebody to do.

View Source
const SpendWaiveFlag = "--no-spend-gate"

SpendWaiveFlag is the way past the gate, spelled once.

View Source
const SpendWaiverPrefix = "Spend gate waived: "

SpendWaiverPrefix is how the CHANGELOG section names a waived spend gate.

View Source
const SprintReleaseFlag = "a directory holding one nova-sprint release as `gh release download <tag> -R <owner>/nova-sprint -D <dir>` writes it " +
	"(<tool>_<tag>_<goos>_<goarch> and SHA256SUMS_<goos>_<goarch>): its tools (nova-sprint, nova-card, nova-work) are verified against " +
	"its checksums and shipped in this release beside the ones built from --source, never compiled here"

SprintReleaseFlag is --sprint-release, said once for build and cycle.

View Source
const SumsDigestPrefix = "SHA256SUMS digest: "

SumsDigestPrefix is how the changelog names the digest of a release's SHA256SUMS, in one place so that what `cut` writes and what a person copies into `adopt --expect-sums` are the same string.

View Source
const SumsFile = "SHA256SUMS"

SumsFile is the name of the checksum file in every artifact directory, in the format `sha256sum -c` reads, because the person verifying a copy by hand should not need this tool to do it.

View Source
const Verbs = `` /* 1655-byte string literal not displayed */

Verbs is the usage block `nova-update help` prints for this verb, and the same five lines docs/SPEC-UPDATE.md carries. Every path is a flag and no flag has a default path: a path guessed from the cwd or from `$HOME` makes a release cut from a laptop and a release cut from a bench mean different things, so the same command is the same release on either host. The one exception is --receipts, and pkg/release/dogfoodgate.go says at length why the gate in front of the definition of done is worth it.

Variables

View Source
var CutNote = "cut classifies the range since the previous tag against the sensitive path list in pkg/release/sensitive.go and docs/SPEC-RELEASE.md " +
	"(" + SensitiveShape + "). A range that touches one of them REFUSES until --security-read names the security reader's read -- a note id or the url of the comment -- " +
	"and the cut then prints `RELEASE CUT SENSITIVE paths=<n> read=<id>` above its receipt. " +
	"A range TOO BIG FOR THE FORGE TO LIST is a different refusal and --security-read does not get past it: a read of a list that may be short is a read of a prefix of the truth. " +
	"Classify such a range from a complete local list instead -- `--local-diff <checkout>` runs `git diff --name-only <previous tag>...<head>` in that checkout, and `--paths-from <file>` writes the answer there for a later cut to read back. " +
	"The tag is annotated, and the annotation carries `sums=<sha256 of SHA256SUMS>` when --sums names the built checksum file, which is the digest `adopt --repo` reads back. " +
	"`build` writes one SHA256SUMS per platform, under <out>/<version>/<goos-goarch>/, and --sums takes one of them: the tag and the CHANGELOG section carry THAT platform's digest, and `adopt --repo` verifies that platform only. " +
	"Every other platform the release built is adopted with --expect-sums-from <out>/<version>/<goos-goarch>/" + DigestFile + " on the host that built it, or --expect-sums <sha256> from the sums= field of its `RELEASE BUILT` line."

CutNote is the gate in front of a tag, said where a person will meet it. It is a var rather than a const because it names the list, and the list has ONE home: composing this from SensitivePaths is why the help cannot fall behind the gate.

View Source
var DefaultReceiptsDir = filepath.Join("nova-working", "dogfood")

DefaultReceiptsDir is where this fleet keeps its receipts, relative to the home directory of whoever is cutting. It is the ONLY path in this package with a default, and it is one on purpose: SPEC-UPDATE rule 1 says no path is guessed, and the reason the rule exists is that a guessed path makes two runs mean different things. A receipts directory is the exception because the alternative -- a release lane that silently skips the gate whenever somebody forgets a flag -- fails in the direction that lets a tool ship. It is used only when it EXISTS, and what was used is named on the line.

View Source
var DogfoodNote = "cut and build run the dogfood gate FIRST -- `nova-check dogfood gate --cli <reference> --receipts <dir>`, in process -- and refuse on an OPEN EDGE: " +
	"a verb somebody ran, that did not do what they needed, and that nobody has run since and said it did. " +
	"A tool is done when it is tested, dogfooded by a non-author on real work, and the feedback is APPLIED; feedback filed is not feedback applied. " +
	"--cli names the command reference and defaults to docs/CLI.md beside the checkout the verb was already given (--changelog for cut, --source for build). " +
	"--receipts names the receipts and defaults to ~/" + DefaultReceiptsDir + " when that directory exists. " +
	"A run with neither is NOT a run that passed: it prints `dogfood-gate=skipped` and names what was missing. " +
	"The way past an open edge is to fix it, or " + DogfoodWaiveFlag + " " + DogfoodReasonFlag + " <why> -- and the waiver is printed on the line AND written into the CHANGELOG section, because a waiver nobody can find later is a gate nobody has."

DogfoodNote is the gate said where a person will meet it: on `release help`, and on `--help` for the two verbs that run it.

View Source
var JourneyNote = "cut runs the journey gate once the head is known: a checkout that ships internal/sprint PROMISES its recovery journeys, and the cut refuses unless --journeys names a `go test -json` run of each, " +
	"under a first line {\"evidence\":\"" + EvidenceKind + "\",\"revision\":<the sha being tagged>,\"functions\":<v>,\"schema\":<v>,\"installed\":[{\"machine\",\"build\",\"revision\"}]}. " +
	"Each journey is read on its own and only a pass proves it: owed, skipped, failed and not-run are incomplete, and a green parent over skipped subtests proves nothing. " +
	"A skip saying `" + PlatformUnavailable + "<platform>` is named, and is not incomplete only for a platform the journey names as optional. " +
	"The way past is " + JourneyWaiveFlag + " " + DogfoodReasonFlag + " <why>, and every incomplete journey is then written into the CHANGELOG section."

JourneyNote is the gate said where a person meets it.

View Source
var PromisedJourneys = []Journey{
	{Package: "internal/sprint", Test: "TestEveryFriendFailureShowsWithinItsBound/harness closed: down within 1 minute"},
	{Package: "internal/sprint", Test: "TestEveryFriendFailureShowsWithinItsBound/session silent: down within 15 minutes of bus silence"},
	{Package: "internal/sprint", Test: "TestEveryFriendFailureShowsWithinItsBound/usage limit: down until the reset, woken after"},
	{Package: "internal/sprint", Test: "TestEveryFriendFailureShowsWithinItsBound/bus credential revoked: an alarm on the first failed send"},
	{Package: "internal/sprint", Test: "TestEveryFriendFailureShowsWithinItsBound/hold: no card left on him, his cards dealt elsewhere"},
}

PromisedJourneys are the recovery journeys a release that ships the sprint package promises (the chaos suite, friend against the sprint's store, lives with the sprint): each way a friend fails, detected within its bound, his cards dealt elsewhere, recovered (docs/SPEC-FRIEND.md, "Chaos").

View Source
var SSHOptions = []string{
	"-o", "BatchMode=yes",
	"-o", "ConnectTimeout=10",
	"-o", "ForwardAgent=no",
}

SSHOptions are the options EVERY invocation carries, in one slice so a test can read the whole policy rather than three call sites (the repository owner, 2026-09-18).

BatchMode so a missing key is a refusal now rather than a password prompt nobody is at the keyboard for. ConnectTimeout so a sleeping bench costs seconds rather than the run. And ForwardAgent=no SAID OUT LOUD rather than left to the default or to whatever ~/.ssh/config on the adopting host says: this verb runs on the one host that holds keys to the whole fleet, and forwarding that agent to a bench would put the fleet's trust inside a machine the release is being pushed TO. A default is not a decision; this is.

There is no -i here and there never will be: a key named on argv is a key in every `ps` on the box. ssh finds its own identity.

View Source
var SensitivePaths = []string{
	"cmd/nova-sandbox/",
	"cmd/nova-secrets/",
	"infra/image/",
	"pkg/sandbox/",
	"pkg/secrets/",
	"profiles/",
	"tools/sandboxcheck/",
}

SensitivePaths is THE LIST, and this file is the one place in code it exists.

SPEC-RELEASE.md decision 1: a release is the moment work stops being a diff somebody can revert and starts being binaries on every bench in the fleet, so the ranges that touch the parts of this estate a mistake cannot be taken back from -- the secret store, the sandbox that holds a worker and the text of its wall, the image every bench boots -- are not cut on the judgement of whoever is at the keyboard. They are cut after he has read them, and the read is NAMED on the command line so the receipt says who vouched for it.

Each entry is a DIRECTORY PREFIX, trailing slash included, and the slash is load-bearing: `pkg/secrets` without it would also catch `pkg/secrets<sibling>/`, and a list that classifies by accident is a list nobody can reason about. Matching is by prefix and by nothing else -- no guessing from a file name, no substring anywhere in the path.

docs/SPEC-RELEASE.md carries the same list in the same order, and internal/ci's TestTheSensitivePathListIsTheSameInTheCodeAndInTheSpec fails when the two disagree. Two copies of a security list drift, and the copy that drifts is always the one nobody is running; this one is the one that runs, so a path is added HERE and the spec is updated in the same commit.

View Source
var SensitiveShape = strings.Join(SensitivePaths, ", ")

SensitiveShape is the list in one phrase, for the help and for a refusal that wants to say what the gate covers. It is COMPOSED from SensitivePaths rather than written out beside it, which is the whole reason the help cannot fall behind the gate.

View Source
var SpendNote = "cut runs the spend gate once the previous tag is known: over the release's window (since the previous tag's commit, or --spend-since <RFC3339>, from the start of that UTC day to now), " +
	"each paid provider's own count of its spend is set beside what the sprint's store (--spend-store <addr>) recorded of it, and each subscription friend's harness receipts (--spend-receipts <file>) beside the tokens the store recorded of them. " +
	"A gap over 5% of the provider's own figure refuses, printing `SPEND provider=<p> store=<$> provider_usd=<$> gap=<$> share=<%>`; a provider whose readout cannot be read, or a friend with no receipt, refuses naming it, never passes. " +
	"The providers' keys come from the environment as nova-secrets exec delivers them and are never printed. " +
	"The way past is " + SpendWaiveFlag + " " + DogfoodReasonFlag + " <why>, and every row that did not pass is then written into the CHANGELOG section."

SpendNote is the gate said where a person meets it.

View Source
var SpendStore func(ctx context.Context, addr string, getenv func(string) string) (RecordedSpend, error)

SpendStore reads what the sprint's store at addr recorded, logged in from getenv. The store's tables are nova-sprint's, so this package cannot read them itself: a binary that links nova-sprint's store sets it (cmd/nova-sprint, spend_store.go), and where it is nil (nova-update, since nova-sprint left this repository) the gate is refused as unread, and --no-spend-gate --reason is the way past.

Functions

func Annotation

func Annotation(version, sha, sumsDigest string, ciWaiver ...string) string

Annotation is the message the TAG OBJECT carries, composed in one place because it is written by `cut` and read by `adopt` and the two have to agree about where the digest is (the repository owner's decision 2, #1337). A tag is the one thing in this repository that cannot be quietly amended, so what it says about a release is the most durable record the release has. It also carries a waived CI gate, when there was one: the tag is exactly where a person asks why a version shipped red, and the extra lines are appended after the digest so the `sums=` line stays put (docs/SPEC-RELEASE.md section 19).

func ArtifactDir

func ArtifactDir(root, version, goos, goarch string) string

ArtifactDir is where one platform's binaries for one version live, under the root --out or --from names. The version and the platform are both in the path so that one root can hold several releases and several platforms at once, which is what a build bench serving four benches actually holds.

func ExeSuffix

func ExeSuffix(goos string) string

ExeSuffix is what a tool's FILE is called on one platform: `nova-bus` on unix, `nova-bus.exe` on windows. It takes the TARGET's goos, never the host's, because every one of these names is decided for the machine the binary will run on rather than for the machine deciding it: a release cut for a windows bench names windows files, and a release built ON windows names them the same way.

Reading runtime.GOOS at each of these sites instead is the defect this exists to make impossible, and it is a defect that hides: it is right on the host that happens to match and silently wrong on every other, so the artifacts end up called one thing while everything looking for them asks for another. The product half is `adopt` composing the remote command as a bare `nova-update`, which names a path that does not exist on a windows bench.

func ExecVersion

func ExecVersion(ctx context.Context, path string) (string, error)

ExecVersion is the production answer to what the binary at path reports: its own `version` verb, which every tool in this repository answers, under a short deadline because the binary being replaced may be the broken one.

func Ldflags

func Ldflags(version string) string

Ldflags is the stamp a release build carries, the same string tools/ghrelease's ldflags verb composes: one place, so the -X cannot be dropped by an edit to a long build line nobody rereads.

func Main

func Main(name string, args []string, stamp string, out, errs io.Writer) int

Main is the production entry: the verb with every seam at its default, and the one thing a seam cannot default to -- what THIS binary is stamped with, which lives in main and is handed down. `adopt` is the reader: a coordinator older than the release it is fanning out cannot run that release's install, and a coordinator far enough behind cannot adopt at all.

func MarkPulled

func MarkPulled(text, version, note string) (string, error)

MarkPulled writes note under version's section and returns the new text. It is pure, so what a withdrawal does to the record is asserted by a test rather than by reading a file somebody edited afterwards.

A section that already carries a note is returned UNCHANGED: a pull run twice -- which is what happens when the first run refused on one machine -- must not stack two notes. A version the changelog does not describe is a refusal: that is somebody pointing --changelog at the wrong file, and writing the note anywhere else would be worse than not writing it.

func Platform

func Platform(flagValue string) (string, string, error)

Platform is the goos-goarch an artifact directory is named for. A release built here for this host is the fleet's common case, and --platform is the flag for the other one: cross-compiling to a bench from wherever the release was cut.

func PulledNote

func PulledNote(when time.Time, reason string) string

PulledNote is the line a pulled section carries, composed in one place so the mark `pull` writes and the mark it recognises on a second run are the same string.

func ReadDigestFile

func ReadDigestFile(path string) (string, error)

ReadDigestFile reads a DigestFile: the sha256 of a release's SHA256SUMS, as `release build` wrote it. The first whitespace-separated token is taken, so a file produced by `sha256sum SHA256SUMS` -- which is `<digest> SHA256SUMS` -- reads too, and a person who made one by hand that way is not punished for it. The path is LOCAL: this function opens a file. A digest computed on the machine holding the bits is not evidence about a fetch.

func ReadPathsFile

func ReadPathsFile(path, rangeName string) ([]string, error)

ReadPathsFile reads one back, and refuses anything this verb did not write: the wrong first line, the wrong range, a list without the digest line the verb pins its body with, and a body that no longer matches that digest.

func RemoteFrom

func RemoteFrom(value string) (host, dir string, remote bool)

RemoteFrom splits a --from that names another machine, as `host:dir`.

THIS IS THE ANSWER TO THE ONE THING THE DOGFOOD PASS COULD NOT DO. adopt was written assuming it runs on the build host and fans out from there; on this fleet it cannot, because no bench has ssh trust to any other bench -- only the the coordinator's machine does, and 3 of 3 machines refused with `Permission denied (publickey)` (receipt 20260918T144929Z, worker-child). The fix that needs NO NEW TRUST is to run adopt from the host that already has it and let it read the artifacts from the host that built them. A jump host (`ssh -J`) would not have helped: -J forwards the connection but still authenticates to the target with the CALLING host's key, so fanning out from a host would still need that host's key on every bench -- new trust between benches, which is the thing we do not want, and the coordinator's machine is a friend's and not ours to hand out keys for.

The host part must be a machine name of at least two characters, so a windows path (`C:\releases`) reads as a local path rather than as a host called C. That is the one ambiguity a colon introduces, resolved in favour of the local path because it is the common case and the mistake is loud either way.

func RemotePath

func RemotePath(p string) string

RemotePath is the form a path takes INSIDE a remote command, and the only place that decision is made: every backslash folded to a forward slash.

THE FAR SIDE PARSES A POSIX COMMAND LINE, on windows as everywhere else. docs/BENCH-WINDOWS.md names the windows bench's ssh shell as Git Bash (`C:\Program Files\Git\bin\bash.exe`), or native OpenSSH with Bash in sshd_config, and the windows checks in pulse/fleetstandard.go at 39e472aa0 are POSIX shell that reach for `powershell.exe -NoProfile -Command '...'` only for the questions only PowerShell can answer. In that shell a backslash is an ESCAPE: `C:\Users\nova` arrives as `C:Usersnova`, silently, and the machine then refuses about a path nobody typed -- which is the worst kind of refusal, because the remedy it suggests is to fix a path that was already right. Windows accepts a forward slash in every API and in every one of its own shells, so the fold costs nothing and removes the whole class.

A unix path can carry no backslash -- remotePathShape has never allowed one -- so this changes nothing about any path that is not a windows one.

func Run

func Run(name string, args []string, out, errs io.Writer, deps Deps) int

Run is `nova-update release <verb>`. The verb is dispatched here and each of the four validates its own flags, so a missing flag is named by the verb that wanted it rather than by a shared check that knows about all of them.

func Sensitive

func Sensitive(files []string) []string

Sensitive returns the paths of files that sit under one of SensitivePaths, deduplicated and sorted, so that a refusal naming them reads the same way twice and a receipt counting them counts files rather than mentions.

func SumsInAnnotation

func SumsInAnnotation(message string) string

SumsInAnnotation reads the digest back out of a tag's message, or "" when the tag carries none -- which is what a release cut before decision 2, or one cut without --sums, looks like from here.

func ToolFile

func ToolFile(tool, goos string) string

ToolFile is the file one tool installs under on the target platform. Every place in this package that turns a tool NAME into a FILE goes through it.

func Tools

func Tools(source string) ([]string, error)

Tools lists the shipped set: every cmd/nova-* directory in the source tree, discovered by walking it rather than from a list. release.yml makes the same argument at greater length -- a tool added tomorrow ships on the day it appears rather than on the day somebody remembers a list.

func ValidRemotePathOn

func ValidRemotePathOn(goos, what, p string) error

ValidRemotePathOn refuses anything that could be more than a path on the far side. It also refuses a RELATIVE path, because the binary this verb runs there must be named absolutely: a relative path resolves against whatever directory the remote shell happens to start in, and a bare name would resolve against $PATH -- which is how a machine ends up running a nova-update that is not the one just verified and sent.

goos is the TARGET's, and the only thing it decides is whether the drive form is a path at all. `C:\Users\nova\.local\bin` on a linux bench is not a path that bench has; taking it would compose a remote command whose first token cannot exist, and the machine would answer `command not found` for a mistake made on this side. So the drive form is allowed for the windows target and refused, by name, for every other.

func ValidSecurityRead

func ValidSecurityRead(id string) error

ValidSecurityRead holds --security-read to the field law before anything is tagged. A read nobody could print is a read nobody could look up, and the whole value of naming it is that a person reading the receipt in six months can go and find what he actually said.

func ValidVersion

func ValidVersion(v string) error

ValidVersion holds a release version to the same shape tools/ghrelease's ldflags verb refuses at, and for the same reasons: the string travels into `-X main.version=`, into a printf format, and into a one-line field. Whitespace splits the linker flag, `%` reads a directive that was never supplied, and `=` is what both the stamp assertion and pkg/oneline treat as a separator -- so a version carrying any of them is a version no later step could check. Refused here, before anything is built or tagged.

func VerbUsage

func VerbUsage(verb string) string

VerbUsage is the one usage line for one release verb, so that `--help` on a verb answers about THAT verb. A person who asked about `adopt` did not ask to re-read `cut`.

func VerifyArtifacts

func VerifyArtifacts(dir string, arts []Artifact) (int, error)

VerifyArtifacts checks every artifact in dir against the checksums the build recorded. It is one function because two callers need exactly this: `install` before its first rename, and `adopt` after fetching a release from another machine -- a truncated fetch caught once on the adopting host is one refusal rather than one per machine. It returns HOW MANY it checked, and `build` prints that number rather than the length of the list it was given. The two are equal when the check ran and there is no way to print the number without running it -- which is the point: `verified=21` computed from a slice length would be a claim about work that may not have happened, and a claim like that is worse than no claim.

func VersionsUnder

func VersionsUnder(root, goos, goarch string) []string

VersionsUnder lists the releases an artifact root holds for one platform: a directory whose <version>/<goos>-<goarch>/SHA256SUMS exists. A directory that is not a release is not a candidate, so a stray folder never becomes one.

func WritePathsFile

func WritePathsFile(path, rangeName string, files []string) error

WritePathsFile records the complete list, the range it is the list for, and the digest of the list itself. The digest is computed with the package's own sumOf (incremental.go).

Types

type Ansible

type Ansible interface {
	Play(ctx context.Context, argv []string) (string, error)
}

Ansible is the edge to ansible-playbook: one run, its output whole.

type Artifact

type Artifact struct {
	Name string
	Sum  string
}

Artifact is one shipped binary: the name it installs under and the checksum the build recorded for it.

func ReadSums

func ReadSums(dir string) ([]Artifact, error)

ReadSums reads an artifact directory's checksum file. It is the whole of what `install` trusts about a directory it did not build: the names come from the file, so a binary somebody dropped into the directory afterwards is not part of the release and is not installed.

type CheckRun

type CheckRun struct {
	Name       string
	Status     string // queued, in_progress, completed
	Conclusion string // success, failure, cancelled, timed_out, skipped, neutral
}

CheckRun is one CI check on a commit, as the forge reports it: the name a person reads in the checks list, the run's status, and its conclusion once it has one. A run that has not completed has an empty Conclusion, which is why both fields are here -- a pending run is not a green one, and reading only the conclusion would make it look like a neutral one.

type Commit

type Commit struct {
	SHA     string
	Message string
}

Commit is one commit in a range, with the whole message: the subject carries the pull request number a squash merge writes, and the body of an integration batch carries the members it rolled up. Both are read from this one field, so the whole changelog is one call to the forge rather than one call per pull request.

type Deps

type Deps struct {
	Forge     Forge
	SSH       SSH
	Toolchain Toolchain
	// Git is the local checkout `cut --local-diff` reads the complete path
	// list out of when the forge's compare is at its ceiling.
	Git Git
	// Dogfood is the definition-of-done gate `cut` and `build` run first. A
	// nil Dogfood is ReadDogfood, which reads the command reference and the
	// receipts off disk and reaches nothing else.
	Dogfood Dogfood
	// Journeys is the recovery journeys `cut` holds the release to
	// (journeygate.go). A nil Journeys is the checkout's own promise:
	// PromisedJourneys, for each package the checkout ships.
	Journeys []Journey
	// Spend is the spend gate's readouts (spendcheck.go): the store's recorded spend,
	// each paid provider's own, the subscription friends' receipts. A nil Spend is
	// ProductionSpend, from --spend-store and --spend-receipts and the environment.
	Spend *SpendSources
	Now   func() time.Time
	// Self answers what the nova-update RUNNING THIS is stamped with. It is a
	// seam rather than a constant because this package is a library and the
	// stamp lives in main; a nil Self means `adopt` cannot compare its own
	// version with the release's and does not pretend to.
	Self func() string
	// VersionOf answers what the binary at path reports for itself. It is a
	// seam because `install`'s skip decision is the one place this package
	// runs a binary it is about to replace.
	VersionOf func(ctx context.Context, path string) (string, error)
	// Source is the checkout `build` records and `--incremental` diffs
	// (incremental.go); nil is ExecSource.
	Source Source
	// Ansible runs the tools play for `cycle` (cycle.go); nil is
	// ExecAnsible with --ansible.
	Ansible Ansible
}

Deps are the seams. A zero Deps is the production one: the forge is gh, the remote is ssh, the compiler is go, the clock is the machine's. A test fills in what it needs and nothing it fills in can reach the network.

type Dogfood

type Dogfood func(cli, receipts, cmd string) (DogfoodVerdict, error)

Dogfood is the seam. A nil Dogfood in Deps is the production one, which reads the reference, the receipts and the cmd/ directory off disk and reaches nothing else -- no forge, no network, no shell.

type DogfoodVerdict

type DogfoodVerdict struct {
	Verbs int
	Open  int
	// Shipped is how many tools the gate judged, and Outside how many
	// receipts it set aside because they name a tool the release does not
	// ship. Both are said on the line: evidence left out is counted.
	Shipped int
	Outside int
	// Findings are the gate's own lines, one per open edge, in the words
	// `nova-check dogfood gate` prints them. The release lane says what the
	// gate says rather than paraphrasing it: two spellings of one finding is
	// one of them going stale.
	Findings []string
}

DogfoodVerdict is what the gate answers: what it read, and what it found.

func ReadDogfood

func ReadDogfood(cli, receipts, cmd string) (DogfoodVerdict, error)

ReadDogfood is that production gate. cmd is the checkout's cmd/ directory: the tools under it are the shipped set, and the gate judges only them. An empty cmd judges every tool the receipts name, which is the stricter read.

type EvidenceHeader

type EvidenceHeader struct {
	Evidence  string             `json:"evidence"`
	Revision  string             `json:"revision"`
	Functions string             `json:"functions"`
	Schema    string             `json:"schema"`
	Installed []InstalledReceipt `json:"installed"`
}

EvidenceHeader is the evidence file's first line. Every field is required: a journey that passed is proof about one revision, one installed build and one set of function and schema versions, and evidence that cannot say which is proof about nothing in particular.

type ExecAnsible

type ExecAnsible struct{ Path string }

ExecAnsible is the production play runner: the named ansible-playbook, stdin closed (no prompt can wait for a person), stdout and stderr together.

func (ExecAnsible) Play

func (a ExecAnsible) Play(ctx context.Context, argv []string) (string, error)

type ExecGit

type ExecGit struct{}

ExecGit is the production checkout reader: one `git -C <dir> diff --name-only <base>...<head>`, which is a READ and the only git this package ever runs. It is here rather than in cut.go for the same reason gh and ssh are: every edge to a subprocess is in this file, where it can be read whole.

func (ExecGit) DiffNames

func (ExecGit) DiffNames(ctx context.Context, dir, base, head string) ([]string, error)

DiffNames lists the paths a range touched, with THREE dots -- what head carries since the merge base, which is the same range the forge's compare answers and therefore the same question, without the forge's file-count cap.

type ExecSSH

type ExecSSH struct {
	Path  string
	Guard *testguard.Guard
}

ExecSSH is the production remote. The ssh binary is named by --ssh rather than found on PATH, because "no cwd dependence, every path a flag" applies to the program as much as to the directories: a bench with two ssh binaries should not be a coin toss.

Guard is the per-test host guard this seam consults: a test arms an isolated testguard.NewGuard(true) as a field on the value under test, so it needs neither t.Setenv nor a process-wide reload. The nil default is the production path: the package-level guard, armed from NOVA_TEST_NO_HOST by `make test`.

func (ExecSSH) Fetch

func (s ExecSSH) Fetch(ctx context.Context, machine, dir, dest string) (string, error)

Fetch reads a directory FROM the machine into a local one, the mirror of Send: the remote tars to stdout and the stream is unpacked here, in Go, so the entry names are checked by this process rather than trusted to a local tar. It is what makes `--from host:dir` work -- the host that has the ssh trust adopting a release that lives on the host that has the cores.

func (ExecSSH) Run

func (s ExecSSH) Run(ctx context.Context, machine string, argv []string) (string, error)

Run executes argv on the machine. The arguments are handed to ssh as separate argv entries; the remote's own shell still reassembles them, so every value that reaches here has been checked by its caller (the machine name against machineName, the version against ValidVersion, the paths by the flags that named them).

func (ExecSSH) Send

func (s ExecSSH) Send(ctx context.Context, machine, dir, dest string) (string, error)

Send copies a directory to the machine as a tar stream on ssh's stdin. The tar is written HERE, in Go, rather than by a local `tar` piped into a remote one: the thing this replaces was two ssh invocations joined by a shell pipe, where a failure in the first was invisible to the second.

type ExecSource

type ExecSource struct{}

ExecSource is the production Source: git and go in the checkout, each a read.

func (ExecSource) Changed

func (ExecSource) Changed(ctx context.Context, dir, base, head string) ([]string, error)

func (ExecSource) GoVersion

func (ExecSource) GoVersion(ctx context.Context) (string, error)

func (ExecSource) Head

func (ExecSource) Head(ctx context.Context, dir string) (string, bool, error)

func (ExecSource) Packages

func (ExecSource) Packages(ctx context.Context, dir, goos, goarch string, pkgs []string) (map[string][]string, error)

Packages is one `go list -deps` per platform for every tool at once: each package's import path, directory, standard flag and transitive imports.

type FileReceipts

type FileReceipts struct{ Path string }

FileReceipts is the subscription friends' harness receipts as one file: {"evidence":"spend-receipts","from":<RFC3339>,"to":<RFC3339>,"friends":{"<friend>":<tokens>}}, the tokens each friend's harness counted over [from, to). It is read only for a window it covers: from the window's start, to within an hour of its end.

func (FileReceipts) Tokens

func (f FileReceipts) Tokens(_ context.Context, w SpendWindow) (map[string]int64, error)

Tokens is the file's tokens by friend, or why they cannot be read for the window.

type Forge

type Forge interface {
	// HeadSHA resolves a branch to the commit it points at.
	HeadSHA(ctx context.Context, repo, branch string) (string, error)
	// CheckRuns reads every check run recorded against one commit.
	CheckRuns(ctx context.Context, repo, sha string) ([]CheckRun, error)
	// Tags lists the repository's tag names, unordered; the caller picks.
	Tags(ctx context.Context, repo string) ([]string, error)
	// Compare lists the commits in base..head, oldest first.
	Compare(ctx context.Context, repo, base, head string) ([]Commit, error)
	// Files lists the paths base..head touched. It is a separate question
	// from Compare because it is asked for a separate reason -- the
	// classification `cut` makes against SensitivePaths -- and because its
	// answer carries a ceiling of its own (CompareFileCap) that the commit
	// list does not.
	Files(ctx context.Context, repo, base, head string) ([]string, error)
	// Tag creates an ANNOTATED tag at sha: a tag OBJECT carrying message,
	// then the ref pointing at that object. It is the one mutation on this
	// interface and the only one `cut` performs.
	Tag(ctx context.Context, repo, tag, sha, message string) error
	// TagMessage reads an existing annotated tag's message back. It is how
	// `adopt` learns the digest a release was cut with without anybody
	// retyping it: a tag object is a git object, so its message reached the
	// adopting host through the repository rather than through the machine
	// whose bits are being checked against it.
	TagMessage(ctx context.Context, repo, tag string) (string, error)
}

Forge is the edge between this tool and GitHub. Seven questions, one gh invocation each -- Tag is two, for the reason it gives -- and every one of them is a read except Tag.

type GH

type GH struct{ Timeout time.Duration }

GH is the production forge: one `gh` invocation per question, each under the run's deadline. gh carries its own credential; nothing here reads, logs or passes one.

func NewGH

func NewGH(timeout time.Duration) *GH

NewGH returns the production forge.

func (*GH) CheckRuns

func (g *GH) CheckRuns(ctx context.Context, repo, sha string) ([]CheckRun, error)

CheckRuns reads every check run recorded against a commit, paginated, because this repository runs more than a page of them on a self-hosted fleet and a first page read as the whole set is a green nobody checked.

func (*GH) Compare

func (g *GH) Compare(ctx context.Context, repo, base, head string) ([]Commit, error)

Compare lists the commits between two revisions. ONE call answers the whole changelog: a squash merge's message carries the pull request's title in its subject and its body underneath, so the alternative -- list the merged pull requests, then read each one -- is a hundred calls for the same text.

func (*GH) Files

func (g *GH) Files(ctx context.Context, repo, base, head string) ([]string, error)

Files lists the paths a compare range touched. One call, and the names are deduplicated here because a paginated compare repeats the diff's file list on each page it answers with.

THE ANSWER IS BOUNDED BY THE FORGE at CompareFileCap files, which is why the caller checks for a list at exactly that number rather than trusting a short one; see CompareFileCap for what that means for the classification.

func (*GH) HeadSHA

func (g *GH) HeadSHA(ctx context.Context, repo, branch string) (string, error)

HeadSHA asks the forge, not a local checkout: the commit a release is cut from is the one the forge believes main is at, and a local clone can be behind it by exactly the merge somebody is about to release.

func (*GH) Tag

func (g *GH) Tag(ctx context.Context, repo, tag, sha, message string) error

Tag creates the annotated tag. It is a create, never a force-move: a tag that can be moved is a tag whose binaries and whose source can disagree, which is the one state release.yml spends thirty lines refusing.

func (*GH) TagMessage

func (g *GH) TagMessage(ctx context.Context, repo, tag string) (string, error)

TagMessage reads an annotated tag's message. Two reads: the ref, to learn what it points at, and then the object. A ref pointing at a COMMIT is a lightweight tag -- every tag this tool made before decision 2 -- and it is refused by name rather than answered with an empty message, because "this tag carries no annotation" and "this annotation carries no digest" are two different facts with two different remedies.

func (*GH) TagTime

func (g *GH) TagTime(ctx context.Context, repo, tag string) (time.Time, error)

TagTime is when the tag's commit was made, as the forge says it: the start of the spend window since that tag.

func (*GH) Tags

func (g *GH) Tags(ctx context.Context, repo string) ([]string, error)

Tags lists tag names. The caller picks the highest by semantic order; this returns them in whatever order the forge gave them, because a lexical order from the API is exactly the order that puts v0.15.10 before v0.15.3.

type Git

type Git interface {
	// DiffNames is `git -C dir diff --name-only base...head`: the paths the
	// range touched, THREE dots, so it is what head carries since the merge
	// base rather than every difference between two branches.
	DiffNames(ctx context.Context, dir, base, head string) ([]string, error)
}

Git is the edge to a LOCAL CHECKOUT, and it exists for exactly one question: which paths a range touched, when the forge cannot say.

The forge answers a compare with at most CompareFileCap files, and the sensitive-path gate cannot be run on a prefix of the truth. git in a checkout has no such ceiling. The answer is produced BY THIS VERB rather than pasted in by a person, because a classification gate whose input is hand-written is a gate whose input is whatever somebody remembered.

type GoBuild

type GoBuild struct{}

GoBuild is the production toolchain. CGO is off so the artifact runs on a bench whose libc is not this one's, and the arguments the caller composed -- -trimpath and the -ldflags stamp -- are passed through untouched.

func (GoBuild) Build

func (GoBuild) Build(ctx context.Context, source, pkg, out, goos, goarch string, args []string) (string, error)

Build compiles one package to one output path.

func (GoBuild) Platforms

func (GoBuild) Platforms(ctx context.Context) ([]string, error)

Platforms is `go tool dist list`: every goos/goarch THIS toolchain can build, asked of the toolchain rather than remembered in a table here, so the answer is right for the Go the release is actually being built with.

type InstalledReceipt

type InstalledReceipt struct {
	Machine  string `json:"machine"`
	Build    string `json:"build"`
	Revision string `json:"revision"`
}

InstalledReceipt is one machine the journeys ran against and the build it had installed, at the revision that build was made from.

type Journey

type Journey struct {
	// Package is the test's package, relative to the module root, such as
	// internal/sprint. A checkout without it does not ship the capability, and
	// does not promise its journeys.
	Package string
	// Test is the test's name as the source spells it, Parent/subtest, with the
	// spaces `go test` turns into underscores.
	Test string
	// Optional names the platforms whose absence is a skip rather than a broken
	// promise: a journey that needs a real harness on a windows bench may say the
	// bench did not answer.
	Optional []string
}

Journey is one promised recovery journey: a test that has to have run and passed at the release revision.

type JourneyRecord

type JourneyRecord struct {
	State      string
	Revision   string
	Header     EvidenceHeader
	Proven     int
	Reason     string
	Incomplete []JourneyResult
}

JourneyRecord is what the gate leaves for the CHANGELOG section: the bound evidence when it passed, the incomplete journeys when it was waived.

type JourneyResult

type JourneyResult struct {
	Journey Journey
	State   string
	Detail  string
}

JourneyResult is what the evidence says about one promised journey.

type Machine

type Machine struct {
	Name string
	// Bin and Dest override --bin and --dest for this machine. Empty means
	// the flag's value, which is the ordinary case.
	Bin, Dest string
}

Machine is one line of the --machines file: which machine, and optionally where ITS tools go. The fleet has three different home directories, so a single --bin is right for most machines and wrong for one; the columns are how that one is said in the file rather than by a second run with different flags -- which is a second chance to get the version wrong.

func Machines

func Machines(r io.Reader) ([]Machine, error)

Machines reads the machine list. MachinesShape is that format said once: one machine per line, optionally followed by TAB-separated --bin and --dest overrides for that machine, blanks and `#` comments skipped, every name checked before ssh is reached. The file is a flag because the fleet is not a constant -- it was four benches, then five, and the day the iMac Pro joined nothing in a tool should have needed editing.

type NoReadout

type NoReadout struct{ Name, Why string }

NoReadout is a provider whose own count cannot be read: every window is unread, with why.

func (NoReadout) Provider

func (n NoReadout) Provider() string

Provider is the provider's name.

func (NoReadout) Spend

Spend is always the error naming why.

type OneMachine

type OneMachine struct {
	// Version is the release to adopt: the one the coordinator's machine runs.
	Version string
	// Flags are adopt's flags but --machines, --version and --dry-run, as the
	// coordinator would type them (--ssh, --from, --bin, --dest, the stage's
	// digest, --no-certify or the certification's three).
	Flags []string
	// Dir is where the one-machine lists are written; "" is os.TempDir.
	Dir string
	// Deps are adopt's (Run); the zero value is the real ssh and clock.
	Deps Deps
	// contains filtered or unexported fields
}

OneMachine adopts one release onto one machine at a time, through adopt itself: the sprint's tick hands it a fleet member back from down (docs/SPEC-SPRINT.md section 5, "Back from down: adopt the latest"; docs/SPEC-RELEASE.md, "Adopting one machine"). An adoption is three runs of adopt with a machine list of that machine alone: --dry-run, which reads the version it has installed (installed= of RELEASE WOULD ADOPT); the adopt; and --dry-run again, which reads the version back. The tick calls Start and Adoption from inside a plan, so the runs go on beside it, and one machine has at most one adoption in flight.

func (*OneMachine) Run

func (o *OneMachine) Run(machine, episode string) (OneRun, bool)

Run is the machine's adoption of the episode, false when there is none.

func (*OneMachine) Start

func (o *OneMachine) Start(machine, version, episode string) bool

Start begins the adoption of version on machine for the episode, beside the caller. It is false, and starts nothing, while an adoption of that machine is in flight, or when the machine already has a run of the episode.

type OneRun

type OneRun struct {
	Episode string
	Running bool
	From    string
	To      string
	Err     string
}

OneRun is a machine's adoption of one episode: running until it ends, the version installed before and read back after, and why it failed.

type OpenRouterSpend

type OpenRouterSpend struct {
	RT     http.RoundTripper
	Getenv func(string) string
}

OpenRouterSpend is openrouter's own count of a window: the activity's days from the window's first through yesterday, and the key's count of today when the window reaches it.

func (OpenRouterSpend) Provider

func (OpenRouterSpend) Provider() string

Provider is "openrouter".

func (OpenRouterSpend) Spend

Spend is the account's dollars over the window.

type PR

type PR struct {
	Number  int
	Title   string
	Members []int
}

PR is one merged pull request as the changelog prints it. Members is the list of pull requests an integration batch rolled up, read from the batch's own body: a batch line that named only the batch would hide ten pieces of work behind one number.

func PullRequests

func PullRequests(commits []Commit) []PR

PullRequests reads a compare range as a changelog. One commit whose subject ends in `(#n)` is one pull request; a `Merge pull request #n from ...` subject is one too, with its title read from the body. The rest of the message, if it names other pull requests, is that entry's member list.

type ProviderSpend

type ProviderSpend interface {
	Provider() string
	Spend(ctx context.Context, w SpendWindow) (float64, error)
}

ProviderSpend is one paid provider's own count of what was spent with it over a window, in dollars. An error is a readout that could not be read, and refuses.

func SpendReaders

func SpendReaders(rt http.RoundTripper, getenv func(string) string) []ProviderSpend

SpendReaders is the paid providers' readouts over the transport (nil is http.DefaultTransport), their keys read from getenv: openrouter's account activity, and the providers whose own count no endpoint answers, each an error naming why.

type RecordedSpend

type RecordedSpend interface {
	Providers(ctx context.Context, w SpendWindow) ([]string, error)
	Spend(ctx context.Context, provider string, w SpendWindow) (float64, error)
	Tokens(ctx context.Context, w SpendWindow) (map[string]int64, error)
}

RecordedSpend is what the sprint's store recorded over a window: the paid providers it knows of, its dollars of each, and its tokens of each subscription friend.

type SSH

type SSH interface {
	// Run executes argv on machine and returns its combined output.
	Run(ctx context.Context, machine string, argv []string) (string, error)
	// Send copies the local directory tree at dir to dest on machine.
	Send(ctx context.Context, machine, dir, dest string) (string, error)
	// Fetch copies the directory dir ON machine into the local directory
	// dest. It is how the host that has the trust reads a release built on
	// the host that has the cores, without anybody copying it by hand.
	Fetch(ctx context.Context, machine, dir, dest string) (string, error)
}

SSH is the edge to another machine: run a command there, or put a directory there. Both take argv rather than a command line, because the thing this package replaces built its remote commands by pasting shell into shell and the quoting was the part nobody could read.

type Source

type Source interface {
	// Head is the commit the checkout is at and whether its tree is clean:
	// no change to a tracked file and no untracked file.
	Head(ctx context.Context, dir string) (commit string, clean bool, err error)
	// Changed is every path that differs between two commits' trees.
	Changed(ctx context.Context, dir, base, head string) ([]string, error)
	// Packages answers, for one platform, each package's directories relative
	// to dir: its own and every non-standard package it imports, transitively.
	Packages(ctx context.Context, dir, goos, goarch string, pkgs []string) (map[string][]string, error)
	// GoVersion is the version of the go that builds the release.
	GoVersion(ctx context.Context) (string, error)
}

Source is the edge an incremental build asks: where the checkout is, what changed since a recorded commit, which package directories each tool is built from, and which Go builds it. A nil Source in Deps is ExecSource.

type SpendRow

type SpendRow struct {
	Kind   string
	Name   string
	Store  float64
	Own    float64
	Gap    float64 // Own less Store
	Share  float64 // |Gap| over Own; 1 when Own is 0 and Store is not
	Unread string
}

SpendRow is one comparison: a paid provider's dollars (Kind "provider") or a subscription friend's tokens (Kind "friend"). Unread says why the provider's own figure could not be read, "" when it was.

func (SpendRow) Line

func (r SpendRow) Line() string

Line is the row as the gate prints it.

func (SpendRow) Refused

func (r SpendRow) Refused() bool

Refused says the row refuses the cut: unread, or its gap past SpendGapOver.

type SpendSources

type SpendSources struct {
	Store     RecordedSpend
	Providers []ProviderSpend
	Receipts  TokenReceipts
}

SpendSources are the gate's three readouts. A nil Receipts is no receipts readout: a subscription friend the store recorded is then a refusal naming it.

func ProductionSpend

func ProductionSpend(ctx context.Context, o options, w SpendWindow) (SpendSources, error)

ProductionSpend is the gate's sources from the cut's flags: the store at --spend-store, the providers' readouts (SpendReaders), the receipts at --spend-receipts.

type SpendVerdict

type SpendVerdict struct {
	Window SpendWindow
	Rows   []SpendRow
}

SpendVerdict is what the gate found over its window.

func CheckSpend

func CheckSpend(ctx context.Context, src SpendSources, w SpendWindow) (SpendVerdict, error)

CheckSpend sets each paid provider's own spend over the window beside the store's, and each subscription friend's receipts beside the store's tokens of them. The providers checked are every one the store knows of and every one a readout is given for; one with no readout is unread. An error is the store unread, which refuses whole.

func (SpendVerdict) Refused

func (v SpendVerdict) Refused() []SpendRow

Refused is the rows that refuse the cut.

type SpendWindow

type SpendWindow struct {
	From, To time.Time
}

SpendWindow is the release's window: [From, To), From at the start of its UTC day (the providers count by the UTC day).

func SpendWindowFrom

func SpendWindowFrom(since, now time.Time) SpendWindow

SpendWindowFrom is the window from since to now, since taken back to the start of its UTC day.

func (SpendWindow) String

func (w SpendWindow) String() string

String is the window as a line field says it.

type TagTimer

type TagTimer interface {
	TagTime(ctx context.Context, repo, tag string) (time.Time, error)
}

TagTimer is a forge that can say when a tag's commit was made: the start of the spend window. GH is one.

type TokenReceipts

type TokenReceipts interface {
	Tokens(ctx context.Context, w SpendWindow) (map[string]int64, error)
}

TokenReceipts is the subscription friends' harnesses' own counts of the tokens each used over a window, by friend. An error is receipts that could not be read, and refuses.

type Toolchain

type Toolchain interface {
	Build(ctx context.Context, source, pkg, out, goos, goarch string, args []string) (string, error)
	// Platforms is every `goos/goarch` this toolchain can compile for, as
	// `go tool dist list` prints it. It is ASKED rather than written out in
	// this file because a list here is a list that is right on the day it is
	// written -- and the fourth release dogfood found out what the other kind
	// costs: `--platform darwin-arm64,darwin-amd64` reached the compiler
	// whole, failed at tool 1 of 21 with the compiler's own `unsupported
	// GOOS/GOARCH pair`, and left an empty directory of that name in the
	// release tree for somebody to find later.
	Platforms(ctx context.Context) ([]string, error)
}

Toolchain is the edge to `go build`. The arguments are handed over whole, so that a test asserting -trimpath and the -ldflags stamp is asserting the exact strings the compiler is given rather than a summary of them.

Jump to

Keyboard shortcuts

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