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
127 lines
5.1 KiB
Go
127 lines
5.1 KiB
Go
package delivery
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"sort"
|
|
"strings"
|
|
)
|
|
|
|
// SourcePrefix2 opens every source fingerprint of the new form.
|
|
const SourcePrefix2 = "src2:"
|
|
|
|
// NoPackageVersion opens the reason a module has no fingerprint while a mesh package it uses has no published version
|
|
// the indexer holds (novox/hq ADR 0573 §2); the packages follow, by module path.
|
|
const NoPackageVersion = "no published version of the package "
|
|
|
|
// Toolchain2 is what an artifact's toolchain enters the new fingerprint as (novox/hq ADR 0405 clause 7): the
|
|
// artifact's language, the installed toolchain's version and the build module's recipe version, in place of the
|
|
// toolchain image's digest. A toolchain update so changes the fingerprints of that language's modules only.
|
|
type Toolchain2 struct {
|
|
Language string
|
|
Version string
|
|
Recipe string
|
|
}
|
|
|
|
// SourceInputs2 are what a module's source fingerprint of the new form is of (novox/hq ADR 0429 clause 5 as ADR 0437
|
|
// clause 5 changed it; ADR 0453 clause 6). It differs from SourceInputs in exactly four things: a base enters by its
|
|
// module's fingerprint (src2:...), not its image digest; the builder enters by its own module's fingerprint, not its
|
|
// executable's digest; an artifact that resolves packages when it is built enters by the hash of its lock file; and
|
|
// an artifact's toolchain enters as Toolchain2. Module, Tree, Contexts and Unpinned are as in SourceInputs.
|
|
//
|
|
// Locks names the artifacts that resolve packages when built, each with the hash of its lock file; an entry with an
|
|
// empty hash is such an artifact with no lock, which has no fingerprint. An artifact that resolves nothing is not in
|
|
// Locks.
|
|
type SourceInputs2 struct {
|
|
Module string
|
|
Tree string
|
|
Bases []string
|
|
Builder string
|
|
Contexts map[string]string
|
|
Locks map[string]string
|
|
Toolchains map[string]Toolchain2
|
|
// Packages are the version of each mesh package the module uses, by package (novox/hq ADR 0573 §2): a package with
|
|
// no version gives no fingerprint. None leaves the fingerprint as it was before packages entered it.
|
|
Packages map[string]string
|
|
Unpinned string
|
|
}
|
|
|
|
// SourceFingerprint2 is the module's source fingerprint of the new form, `src2:` and the hex sha256 of its inputs'
|
|
// lines, or none and the reason when the source does not pin the build: Unpinned when set, "no tree" when Tree is
|
|
// empty, "no lock" when an artifact resolves packages and has no lock. It is a pure function of its inputs: the order
|
|
// of maps and a base given twice change nothing. The lines are "module <m>", "tree <t>" and "builder <fp>", then
|
|
// "base <fp>" (once each), "context <artifact> <tree>", "lock <artifact> <hash>", "toolchain <artifact> <language>
|
|
// <version> <recipe>" and "package <package> <version>", sorted, all joined by a newline. A module that uses no mesh
|
|
// package has no package line, so its fingerprint is the one it had before packages entered (novox/hq ADR 0573 §2).
|
|
func SourceFingerprint2(in SourceInputs2) (fp, none string) {
|
|
if in.Unpinned != "" {
|
|
return "", in.Unpinned
|
|
}
|
|
if in.Tree == "" {
|
|
return "", "no tree"
|
|
}
|
|
for _, h := range in.Locks {
|
|
if h == "" {
|
|
return "", "no lock"
|
|
}
|
|
}
|
|
var unpublished []string
|
|
for p, v := range in.Packages {
|
|
if v == "" {
|
|
unpublished = append(unpublished, p)
|
|
}
|
|
}
|
|
if len(unpublished) > 0 {
|
|
sort.Strings(unpublished)
|
|
return "", NoPackageVersion + strings.Join(unpublished, ", ")
|
|
}
|
|
lines := []string{"module " + in.Module, "tree " + in.Tree, "builder " + in.Builder}
|
|
bases := map[string]bool{}
|
|
for _, b := range in.Bases {
|
|
bases[b] = true
|
|
}
|
|
for b := range bases {
|
|
lines = append(lines, "base "+b)
|
|
}
|
|
for a, t := range in.Contexts {
|
|
lines = append(lines, "context "+a+" "+t)
|
|
}
|
|
for a, h := range in.Locks {
|
|
lines = append(lines, "lock "+a+" "+h)
|
|
}
|
|
for a, t := range in.Toolchains {
|
|
lines = append(lines, "toolchain "+a+" "+t.Language+" "+t.Version+" "+t.Recipe)
|
|
}
|
|
for p, v := range in.Packages {
|
|
lines = append(lines, "package "+p+" "+v)
|
|
}
|
|
sort.Strings(lines[3:])
|
|
sum := sha256.Sum256([]byte(strings.Join(lines, "\n")))
|
|
return SourcePrefix2 + hex.EncodeToString(sum[:]), ""
|
|
}
|
|
|
|
// FingerprintKeyPart is a new-form fingerprint as a key part: its 64 hex digits without the prefix, since a bus key
|
|
// may not hold ':'. It refuses a value that is not `src2:` and 64 lowercase hex digits, a src1 fingerprint included,
|
|
// naming it.
|
|
func FingerprintKeyPart(fp string) (string, error) {
|
|
part, ok := strings.CutPrefix(fp, SourcePrefix2)
|
|
if !ok || !isHex64(part) {
|
|
return "", fmt.Errorf("delivery: %q is not a fingerprint of the new form, %s and 64 lowercase hex digits", fp, SourcePrefix2)
|
|
}
|
|
return part, nil
|
|
}
|
|
|
|
// ParseFingerprintKeyPart is the inverse of FingerprintKeyPart. It refuses a part that is not 64 lowercase hex
|
|
// digits, a src1 fingerprint or a prefixed value included, naming it.
|
|
func ParseFingerprintKeyPart(part string) (string, error) {
|
|
if !isHex64(part) {
|
|
return "", fmt.Errorf("delivery: %q is not the key part of a fingerprint, 64 lowercase hex digits", part)
|
|
}
|
|
return SourcePrefix2 + part, nil
|
|
}
|
|
|
|
func isHex64(s string) bool {
|
|
return len(s) == 64 && strings.Trim(s, "0123456789abcdef") == ""
|
|
}
|