Files
mesh-controller/internal/inventory/source.go
T
jschoubben c795915315
mesh/merge-gate head c79591531575 merged into trunk aa02e7bf4e78 — pass: builds build-agent, mesh-controller → ace, g14, novox, shanks; no bus step; every …
mesh/repo-check head c79591531575 merged into trunk aa02e7bf4e78 — pass: its merge-check.sh passed
mesh/delivery delivered
Keep builds on the bus under their module, so no lookup reads the bucket whole (issue 1121, ADR 0427)
Milestone 5 moved 8,599 build records onto the bus under bare build ids, each
carrying its module's whole manifest. Every build lookup then read the whole
bucket, and a delivery plan's send did that per module per machine while it
held the plans and the machine's declaration: the send of build-agent never
finished, and every other act and the status waited behind it.

Builds now live under <module>.build.<id> (requests under req.<id>, id.<id>
names a build's module), with pointer keys newest, worked, held,
commit.<sha> and source.<reuse key> that move only to a newer build and are
read as hints, repaired when wrong. Manifests are kept once in the same
bucket under manifest.sha256-<hex>, so no grant the running bus lacks is
needed. The old keys are laid out once at start, idempotently, and left
unread. The calls a plan look makes on the silent clocks while it holds the
plans are bounded, since the vendored kv.Keys ignores its context.
2026-10-18 13:32:31 +02:00

221 lines
7.5 KiB
Go

package inventory
import (
"context"
"encoding/json"
"github.com/novox/mesh-controller/internal/chain"
)
// A build whose source is unchanged is never a move (novox/hq issue 280).
//
// The builder says what each build was made from, hashed (its source fingerprint). Two successful
// builds of a module with one fingerprint are one build, whatever digests they made — an image is not
// byte-reproducible, and reading a rebuild's new image digest as a new build made a merge that never
// touched the bus demand a planned bus upgrade before anything could be sent to the machine running
// it. So:
//
// - a module's builds, newest request first, fall into runs of one fingerprint; **the oldest build of
// the newest run stands for the run**: its artifacts are the ones the mesh registers and hands
// every module that stands on it (Held), so a rebuild of an unchanged source changes nothing any
// machine is sent, and nothing standing on it moves either;
// - a build that failed its gate ends a run: what was put back is never what a later build stands for;
// - an empty fingerprint — a builder that predates it, a source that does not pin its build — is a
// run of its own, as every build was before.
// sourceRow is one successful build of a module, as the runs are read.
type sourceRow struct {
id, commit, fingerprint string
made []byte
manifest []byte
manifestDigest string
failedGate bool
}
// sourceRows is every successful build of each module (of one module, when named), newest request
// first.
func (i *Inventory) sourceRows(ctx context.Context, module string) (map[string][]sourceRow, []string, error) {
if c, on, err := i.ChainReads(ctx, KindBuilds); err != nil {
return nil, nil, err
} else if on {
return i.chainSourceRows(ctx, c, module)
}
query := `select b.module, b.id, b.commit_hash, b.source_fingerprint, b.made, b.manifest,
coalesce(g.verdict = 'failed', false)
from build b left join build_gate g on g.build = b.id
where b.module is not null and b.module <> '' and b.failed = ''`
args := []any{}
if module != "" {
query += ` and b.module = $1`
args = append(args, module)
}
rows, err := i.store.Pool().Query(ctx, query+` order by b.module, coalesce(b.asked, b.at) desc, b.at desc`, args...)
if err != nil {
return nil, nil, err
}
defer rows.Close()
out := map[string][]sourceRow{}
var order []string
for rows.Next() {
var m string
var r sourceRow
if err := rows.Scan(&m, &r.id, &r.commit, &r.fingerprint, &r.made, &r.manifest, &r.failedGate); err != nil {
return nil, nil, err
}
if _, seen := out[m]; !seen {
order = append(order, m)
}
out[m] = append(out[m], r)
}
return out, order, rows.Err()
}
// standing is, of a module's builds newest first, the index of the build that stands for the build at
// `from`: the oldest of the unbroken run of builds behind it with its source fingerprint.
func standing(builds []sourceRow, from int) int {
at := from
fp := builds[from].fingerprint
if fp == "" || builds[from].failedGate {
return at
}
for j := from + 1; j < len(builds); j++ {
if builds[j].fingerprint != fp || builds[j].failedGate {
break
}
at = j
}
return at
}
// StandingBuild is the build that stands for a module's build (novox/hq issue 280): the oldest build of
// the unbroken run of builds with its source fingerprint, at or before it — itself when its fingerprint
// is empty or it starts the run. Answers its id and the manifest it was recorded with; empty when the
// build is not a successful build of the module on record.
func (i *Inventory) StandingBuild(ctx context.Context, module, build string) (string, []byte, error) {
all, _, err := i.sourceRows(ctx, module)
if err != nil {
return "", nil, err
}
builds := all[module]
for k, b := range builds {
if b.id == build {
s := builds[standing(builds, k)]
if len(s.manifest) == 0 && s.manifestDigest != "" {
// The manifest is kept apart from the record (issue 1121): one Get.
c, on, err := i.ChainReads(ctx, KindBuilds)
if err != nil || !on {
return "", nil, err
}
raw, err := manifestOfRecord(ctx, c, chain.Build{ID: s.id, ManifestDigest: s.manifestDigest})
return s.id, raw, err
}
return s.id, s.manifest, nil
}
}
return "", nil, nil
}
// SourceFingerprints is, per module, per commit, the source fingerprint of the newest successful build
// from it that has one: module → commit → fingerprint. A commit whose builds carry none is absent.
func (i *Inventory) SourceFingerprints(ctx context.Context) (map[string]map[string]string, error) {
if c, on, err := i.ChainReads(ctx, KindBuilds); err != nil {
return nil, err
} else if on {
// Read from the commit pointers, which carry each commit's source fingerprint: no record is read (issue 1121).
return i.chainSourceFingerprints(ctx, c)
}
all, _, err := i.sourceRows(ctx, "")
if err != nil {
return nil, err
}
out := map[string]map[string]string{}
for m, builds := range all {
for _, b := range builds {
if b.fingerprint == "" || b.commit == "" {
continue
}
if out[m] == nil {
out[m] = map[string]string{}
}
if _, seen := out[m][b.commit]; !seen {
out[m][b.commit] = b.fingerprint
}
}
}
return out, nil
}
// BuildSourceFingerprints is SourceFingerprints for one module: commit → fingerprint.
func (i *Inventory) BuildSourceFingerprints(ctx context.Context, module string) (map[string]string, error) {
if c, on, err := i.ChainReads(ctx, KindBuilds); err != nil {
return nil, err
} else if on {
// This module's commit pointers only (issue 1121).
return i.chainBuildSourceFingerprints(ctx, c, module)
}
all, err := i.SourceFingerprints(ctx)
if err != nil {
return nil, err
}
if all[module] == nil {
return map[string]string{}, nil
}
return all[module], nil
}
// heldMade is, per module, what the build standing for its newest successful build made — what Held
// answers (novox/hq issue 280).
func (i *Inventory) heldMade(ctx context.Context) (map[string][]Artifact, error) {
if c, on, err := i.ChainReads(ctx, KindBuilds); err != nil {
return nil, err
} else if on {
return i.chainHeldMade(ctx, c)
}
all, _, err := i.sourceRows(ctx, "")
if err != nil {
return nil, err
}
out := map[string][]Artifact{}
for m, builds := range all {
if len(builds) == 0 {
continue
}
var made []Artifact
if err := json.Unmarshal(builds[standing(builds, 0)].made, &made); err != nil {
// Skipped rather than fatal, as Held always did: the build that needs it says it is missing.
continue
}
out[m] = made
}
return out, nil
}
// SameSourceCommits is the commits of the builds in a build's run (novox/hq issue 280): the build and
// every build behind it back to the one standing for it, all made from one source. Empty when the
// build stands for itself — no earlier build was made from its source — so a commit alone never says
// two builds are one: a dependent rebuilt because its base moved keeps its commit and is a move.
func (i *Inventory) SameSourceCommits(ctx context.Context, module, build string) (map[string]bool, error) {
all, _, err := i.sourceRows(ctx, module)
if err != nil {
return nil, err
}
builds := all[module]
for k, b := range builds {
if b.id != build {
continue
}
s := standing(builds, k)
if s == k {
return nil, nil
}
out := map[string]bool{}
for j := k; j <= s; j++ {
if builds[j].commit != "" {
out[builds[j].commit] = true
}
}
return out, nil
}
return nil, nil
}