The builder announces what it built, and what it was built on top of
Answering and announcing are different acts. The reply goes to whoever asked and is correlated to their request; the announcement says to the whole mesh that a module now exists at a commit, which is what the catalogue places in the module graph (novox/hq ADR 0072). A build nobody asked for still has to be announced, or the graph knows less than the registry does. What it was built on top of is read out of the build's own inputs rather than declared, because a declared list drifts from what the code actually uses (ADR 0009). These are artifact references, which is what a build input names; resolving them to module-versions is the catalogue's work, since it is what knows which module-version published which artifact. Events ride the topic exchange, not the direct one nodes speak over, so the builder's account is granted both: it must be able to answer and to announce. The envelope is the sdk's, reproduced exactly — a second shape would be a second thing for consumers to handle, and they are written against the first. Announcing is not allowed to fail a build. The work was done and was answered; a build reported as failed because saying so failed is a lie about it. Claude-Session: https://claude.ai/code/session_01D6qtiYU3P9jk3pnAXyAFyx
This commit is contained in:
@@ -11,6 +11,7 @@ import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
@@ -42,6 +43,15 @@ type Publisher interface {
|
||||
|
||||
// Result is everything one build produced.
|
||||
type Result struct {
|
||||
// Against is every pinned image this build was built on top of, read out of its own inputs.
|
||||
//
|
||||
// **Derived, not declared** (novox/hq ADR 0009): a declared list of dependencies drifts from
|
||||
// what the code actually uses, and an artifact is out of date when anything it was built
|
||||
// against moved. These are artifact references rather than module-versions, because that is
|
||||
// what a build input names; resolving them to modules is the catalogue's work, since it is
|
||||
// what knows which module-version published which artifact.
|
||||
Against []string
|
||||
|
||||
// Manifest is the module as the mesh should hold it: artifacts resolved to digests.
|
||||
Manifest catalogue.Manifest
|
||||
// Commit is what was built, so "is this current?" is answerable without building again.
|
||||
@@ -124,7 +134,8 @@ func Build(ctx context.Context, run Runner, publish Publisher,
|
||||
if err != nil {
|
||||
return Result{}, err
|
||||
}
|
||||
return Result{Manifest: resolved, Commit: commit, Built: built}, nil
|
||||
return Result{Manifest: resolved, Commit: commit, Built: built,
|
||||
Against: against(within, manifest)}, nil
|
||||
}
|
||||
|
||||
// inside resolves a module's path within a clone, and refuses one that leaves it.
|
||||
@@ -157,6 +168,39 @@ func describe(path string) string {
|
||||
return path
|
||||
}
|
||||
|
||||
// pinnedImage matches an image reference pinned by digest, which is the only kind a build input is
|
||||
// allowed to name — a tag is something somebody else can move under you.
|
||||
var pinnedImage = regexp.MustCompile(`[A-Za-z0-9][A-Za-z0-9._/:-]*@sha256:[0-9a-f]{64}`)
|
||||
|
||||
// against reads what this module's image artifacts are built on top of, out of the files that
|
||||
// build them. Nothing is guessed: a reference that is not written down is not reported.
|
||||
func against(within string, manifest catalogue.Manifest) []string {
|
||||
if manifest.Build == nil {
|
||||
return nil
|
||||
}
|
||||
seen := map[string]bool{}
|
||||
var out []string
|
||||
for _, a := range manifest.Build.Artifacts {
|
||||
if a.Kind != catalogue.ArtifactImage || a.From == "" {
|
||||
continue
|
||||
}
|
||||
body, err := os.ReadFile(filepath.Join(within, a.From))
|
||||
if err != nil {
|
||||
// Not fatal: the build itself already failed if this file was needed and missing, and
|
||||
// reporting no edges is honest where inventing them would not be.
|
||||
continue
|
||||
}
|
||||
for _, found := range pinnedImage.FindAllString(string(body), -1) {
|
||||
if !seen[found] {
|
||||
seen[found] = true
|
||||
out = append(out, found)
|
||||
}
|
||||
}
|
||||
}
|
||||
sort.Strings(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// ManifestName is the one file a module repository must have.
|
||||
//
|
||||
// At the root, and named the same in every repository. A convention somebody can look for beats a
|
||||
|
||||
Reference in New Issue
Block a user