package inventory import ( "context" "encoding/json" ) // 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 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) { 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)] 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) { 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) { 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) { 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 }