Carry mesh package versions in build asks and fingerprints

ADR 0573 §2 has every module build against the latest published version of
each mesh package it uses, chosen by the planner and written by the build,
never pinned by hand (issue 1585, S4). The ask, the fetch request and the
merge preview now carry the packages and their versions, and the new-form
fingerprint takes each version, so a package's trunk merge changes every
dependent's fingerprint. A module using no package keeps its fingerprint.

Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
This commit is contained in:
2026-11-01 01:57:26 +01:00
parent ff5e00f1f8
commit af4e4e60a3
14 changed files with 356 additions and 22 deletions
+29
View File
@@ -253,6 +253,30 @@ type StepAsk struct {
Compose *ComposeJob `json:"compose,omitempty"`
// Needs are what the node that runs the ask must have (AskNeeds); a check ask's alone. Empty: any node.
Needs []string `json:"needs,omitempty"`
// Packages are the version of each mesh package the build or the check builds against, by its module path (a Go
// module's path, git.novox.be/novox/mesh-sdk/go; the shape takes a seat's package too), whatever version the
// repository's go.mod names (novox/hq ADR 0573 §2, §4; issue 1585): a Go package's version is a full commit, the
// trunk merge the indexer holds as the package's latest published version or, in a delivery group's check, the
// head of the group's member that publishes it. The build module writes each into its workspace, checksum-verified,
// and names them in its log (PackageVersions). A compose takes none: it builds the mesh-sdk its reader names.
Packages PackageVersions `json:"packages,omitempty"`
}
// PackageVersions are the versions of the mesh packages a build or a check builds against, by package (a Go module's
// path), each a full commit (novox/hq ADR 0573 §2).
type PackageVersions map[string]string
// Check refuses a package that is no module path or a version that is no full commit, naming it.
func (p PackageVersions) Check() error {
for pkg, version := range p {
if pkg == "" || strings.ContainsAny(pkg, " \t\n@") || strings.HasPrefix(pkg, "/") {
return fmt.Errorf("delivery: the package %q is no module path", pkg)
}
if !askCommitOK.MatchString(version) {
return fmt.Errorf("delivery: the package %s names the version %q, which is no full commit", pkg, version)
}
}
return nil
}
// ComposeJob is a compose ask's job (novox/hq ADR 0482 clause 4, issue 1622): fetch the commit, build the composer
@@ -389,6 +413,11 @@ func (a StepAsk) check() error {
return fmt.Errorf("delivery: the ask %s is a check of an image, which has no tests", a.ID)
case len(a.Needs) > 0 && a.Check == nil:
return fmt.Errorf("delivery: the ask %s names needs and is no check, whose tests alone need a node", a.ID)
case len(a.Packages) > 0 && a.Compose != nil:
return fmt.Errorf("delivery: the ask %s is a compose and names package versions, which a compose does not take", a.ID)
}
if err := a.Packages.Check(); err != nil {
return fmt.Errorf("the ask %s: %w", a.ID, err)
}
for _, n := range a.Needs {
if err := CheckNeed(n); err != nil {