package inventory import ( "context" "encoding/json" "sort" "time" ) // What has been built. // // A build result was answered to whoever asked and kept nowhere, so "when did this last build", // "why did it fail" and "which machine built what is running" had no answer. Failures are recorded // too: one that leaves no trace is indistinguishable from a build nobody asked for, and the // difference is the whole of whether somebody should be looking at something. // Build is one attempt, whichever way it went. type Build struct { ID string Repository string Ref string // Module is empty for a build that failed before it knew what it was building. Module string Commit string // On is the machine that did it. On string // Path is where inside the repository the module lives (novox/hq ADR 0069). Path string // Manifest is the declaration the builder resolved, as it announced it. // // **Kept because the catalogue may not have been listening.** The announcement carries this // and the catalogue turns it into the module's requires/provides edges. On a fresh mesh the // modules built before the catalogue exists are exactly the ones it most needs, so the mesh // has to be able to say afterwards what they declared (novox/hq 04-ISSUES/050). Manifest []byte // Against is every artifact this build stood on, as references rather than module names — // what makes a build edge derived rather than declared (ADR 0009). Against []string // Failed is the builder's own words, empty when it worked. Failed string Made []Artifact At time.Time } // Artifact is one thing a build published. type Artifact struct { Name string `json:"name"` Kind string `json:"kind"` Reference string `json:"reference"` } // Worked reports whether this build produced something. func (b Build) Worked() bool { return b.Failed == "" } // RecordBuild keeps what a builder said. // // Idempotent on the correlation id, because a result can arrive twice: once as the answer to // whoever asked and once on the exchange when nobody was. Recording both would show one build as // two, and which of the two is real is not a question anybody could answer afterwards. func (i *Inventory) RecordBuild(ctx context.Context, b Build) error { made, err := json.Marshal(b.Made) if err != nil { return err } against, err := json.Marshal(b.Against) if err != nil { return err } var module *string if b.Module != "" { module = &b.Module } _, err = i.store.Pool().Exec(ctx, `insert into build (id, repository, ref, module, commit_hash, built_on, failed, made, source_path, manifest, built_against) values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11) on conflict (id) do nothing`, b.ID, b.Repository, b.Ref, module, b.Commit, b.On, b.Failed, made, b.Path, manifestOrNil(b.Manifest), against) return err } // Builds is what has happened lately, newest first. // // For one module when named, or across the mesh when not. Both are asked: *what happened just // now* after something goes wrong, and *what has happened to this* when deciding whether to // trust it. func (i *Inventory) Builds(ctx context.Context, module string, limit int) ([]Build, error) { if limit <= 0 { limit = 20 } query := `select id, repository, ref, coalesce(module,''), commit_hash, built_on, failed, made, at from build order by at desc limit $1` args := []any{limit} if module != "" { query = `select id, repository, ref, coalesce(module,''), commit_hash, built_on, failed, made, at from build where module = $2 order by at desc limit $1` args = append(args, module) } rows, err := i.store.Pool().Query(ctx, query, args...) if err != nil { return nil, err } defer rows.Close() var out []Build for rows.Next() { var b Build var made []byte if err := rows.Scan(&b.ID, &b.Repository, &b.Ref, &b.Module, &b.Commit, &b.On, &b.Failed, &made, &b.At); err != nil { return nil, err } if err := json.Unmarshal(made, &b.Made); err != nil { return nil, err } out = append(out, b) } return out, rows.Err() } // Held is every artifact this mesh has built, keyed "/". // // **The newest successful build of each module wins**, which is the same rule the rest of the mesh // uses for what a module currently is. A module rebuilt to something broken and then rebuilt again // is at the second one; a module whose last build failed is at the last one that worked, because a // failure published nothing and the thing it published before is still what exists. // // Only successes, and only builds that knew what they were building: a build that failed before it // could read a manifest has no module to be the artifact of. func (i *Inventory) Held(ctx context.Context) (map[string]string, error) { rows, err := i.store.Pool().Query(ctx, `select distinct on (module) module, made from build where module is not null and module <> '' and failed = '' order by module, at desc`) if err != nil { return nil, err } defer rows.Close() held := map[string]string{} for rows.Next() { var module string var raw []byte if err := rows.Scan(&module, &raw); err != nil { return nil, err } var made []Artifact if err := json.Unmarshal(raw, &made); err != nil { // Skipped rather than fatal. One unreadable build record should not stop every other // module's base from being answerable — and the build that needs this one will say // plainly that it is missing. continue } for _, artifact := range made { if artifact.Name == "" || artifact.Reference == "" { continue } held[module+"/"+artifact.Name] = artifact.Reference } } return held, rows.Err() } // BuiltAgainst is what each module's newest successful build stood on, as recorded — the build // edges (ADR 0009). A module whose last build recorded no bases is absent, which is also what a // module standing on nothing looks like: an edge the mesh has not derived is not an edge. func (i *Inventory) BuiltAgainst(ctx context.Context) (map[string][]string, error) { rows, err := i.store.Pool().Query(ctx, `select distinct on (module) module, built_against from build where module is not null and module <> '' and failed = '' order by module, at desc`) if err != nil { return nil, err } defer rows.Close() against := map[string][]string{} for rows.Next() { var module string var raw []byte if err := rows.Scan(&module, &raw); err != nil { return nil, err } if len(raw) == 0 { continue } var refs []string if err := json.Unmarshal(raw, &refs); err != nil { continue } if len(refs) > 0 { against[module] = refs } } return against, rows.Err() } // manifestOrNil keeps the difference between "declared nothing" and "predates this being kept". // // A build recorded before the mesh kept manifests has no manifest, and that is not the same as one // whose manifest was empty. A replay can then say which it is holding instead of inventing an // empty declaration for a module that certainly had one. func manifestOrNil(raw []byte) any { if len(raw) == 0 { return nil } return raw } // Announceable is every build worth telling a catalogue about, oldest first. // // **Oldest first, because a graph is built in the order things happened.** Registering a module // that stands on a base before the base itself would make the edge point at a version the // catalogue has not seen, and the shape of a fresh mesh guarantees that order matters: the base is // always first and always the one that was missed. // // Only builds that succeeded and know what they built. A failure produced no module-version, and // announcing one would put something in the graph that was never made — the same rule the builder // follows when it decides whether to announce at all. // // One row per module and commit: a module built twice at the same commit is one fact, and the // latest row is the one whose artifacts are current. func (i *Inventory) Announceable(ctx context.Context) ([]Build, error) { rows, err := i.store.Pool().Query(ctx, `select distinct on (module, commit_hash) id, repository, ref, module, commit_hash, built_on, failed, made, source_path, manifest, built_against, at from build where failed = '' and module is not null and module <> '' and commit_hash <> '' order by module, commit_hash, at desc`) if err != nil { return nil, err } defer rows.Close() var out []Build for rows.Next() { var b Build var made []byte var manifest, against []byte if err := rows.Scan(&b.ID, &b.Repository, &b.Ref, &b.Module, &b.Commit, &b.On, &b.Failed, &made, &b.Path, &manifest, &against, &b.At); err != nil { return nil, err } if err := json.Unmarshal(made, &b.Made); err != nil { return nil, err } // Null rather than empty is a build recorded before the mesh kept these, and saying so is // the point of keeping them nullable: the replay carries nothing rather than an empty // declaration for a module that certainly had one. if len(manifest) > 0 { b.Manifest = manifest } if len(against) > 0 { if err := json.Unmarshal(against, &b.Against); err != nil { return nil, err } } out = append(out, b) } if err := rows.Err(); err != nil { return nil, err } // Sorted here rather than in the query, because `distinct on` fixes the ordering it needs and // the order that matters to a catalogue is a different one. sort.Slice(out, func(a, b int) bool { return out[a].At.Before(out[b].At) }) return out, nil }