package delivery import ( "encoding/json" "fmt" "slices" "strings" "time" ) // The published mesh packages (novox/hq ADR 0593 ยง2): the indexer alone reads each mesh Go module's package digests // from the forge's listing at its merge and writes, in its own state, the latest published version of the module and // the content digest of each package in it. The builder, the checker and the build modules read this state; no other // part computes a package's version or digest. This is the contract only: the key, the value and its checks. How a // digest is computed is the indexer's own. const ( // PackagesState is the name of the indexer's state of published packages, as its manifest declares it, and // PackagesBucket its bucket: one key per mesh Go module (PackagesKey). PackagesState = "packages" PackagesBucket = IndexerModule + "_" + PackagesState // PackagesHistory is the revisions the packages state keeps per key: the module's newest publications. PackagesHistory = 5 ) // PackagesKey is the key, in PackagesBucket, of a mesh Go module (its module path, such as // git.novox.be/novox/mesh-sdk/go): the path as a key part (EscapeName). It refuses a path that is no mesh Go module's // (PackageRepository). func PackagesKey(module string) (string, error) { if _, _, ok := PackageRepository(module); !ok { return "", fmt.Errorf("delivery: %q is no mesh Go module's path (it starts with %s)", module, ForgeModulePrefix) } return EscapeName(module), nil } // PublishedPackages is the value of a packages key: a mesh Go module as its latest trunk merge published it. Module is // its module path, Repository (`/`) and Dir its repository and its directory there (RootPath for the // root), Commit the merge commit (the version a build pins, StepAsk.Packages), Requires the digest of its go.mod and // go.sum together, and Packages the content digest of each package of the module (its files, tests and sub-packages // excluded), by import path, sorted and each once. Packages is a list, never a map keyed by import path: a state value // refuses a field name ending in a credential word, and a package such as .../internal/token is one (issue 1930). type PublishedPackages struct { V int `json:"v"` Module string `json:"module"` Repository string `json:"repository"` Dir string `json:"dir"` Commit string `json:"commit"` Requires string `json:"requires"` Packages []PackageDigest `json:"packages"` At time.Time `json:"at"` } // DigestOf is the content digest of the package at import path pkg, and whether p holds it. func (p PublishedPackages) DigestOf(pkg string) (string, bool) { i, found := slices.BinarySearchFunc(p.Packages, pkg, func(d PackageDigest, s string) int { return strings.Compare(d.Package, s) }) if !found { return "", false } return p.Packages[i].Digest, true } func (p PublishedPackages) check() error { repository, _, ok := PackageRepository(p.Module) switch { case !ok: return fmt.Errorf("delivery: %q is no mesh Go module's path (it starts with %s)", p.Module, ForgeModulePrefix) case !SameRepository(p.Repository, repository): return fmt.Errorf("delivery: the module %s is published from %s, and the value names the repository %q", p.Module, repository, p.Repository) case p.Dir == "": return fmt.Errorf("delivery: the published module %s names no directory (%q for the repository's root)", p.Module, RootPath) case !askCommitOK.MatchString(p.Commit): return fmt.Errorf("delivery: the published module %s names the commit %q, which is no full commit", p.Module, p.Commit) case p.Requires == "": return fmt.Errorf("delivery: the published module %s names no digest of its requirements", p.Module) case p.At.IsZero(): return fmt.Errorf("delivery: the published module %s names no time", p.Module) } for i, d := range p.Packages { if d.Package != p.Module && !strings.HasPrefix(d.Package, p.Module+"/") { return fmt.Errorf("delivery: the package %q is not in the module %s", d.Package, p.Module) } if d.Digest == "" { return fmt.Errorf("delivery: the package %s names no digest", d.Package) } if i > 0 && p.Packages[i-1].Package >= d.Package { return fmt.Errorf("delivery: the packages of %s are not sorted and each once (%s after %s)", p.Module, d.Package, p.Packages[i-1].Package) } } return nil } // EncodePublishedPackages is p as JSON at Version; it refuses what a reader could not take (check). func EncodePublishedPackages(p PublishedPackages) ([]byte, error) { if err := p.check(); err != nil { return nil, err } p.V = Version return json.Marshal(p) } // DecodePublishedPackages reads a packages value; it refuses an unknown version (ErrUnknownVersion) and what // EncodePublishedPackages refuses. func DecodePublishedPackages(data []byte) (PublishedPackages, error) { var p PublishedPackages if err := decode(data, &p); err != nil { return PublishedPackages{}, err } if err := p.check(); err != nil { return PublishedPackages{}, err } return p, nil } // CheckImports refuses an import set (KeptInputs2.Imports, BuildAnswer.Imports, BuildLogEntry.Imports) that holds a // path that is no mesh package's, or is not sorted and each once. func CheckImports(imports []string) error { for i, pkg := range imports { if _, _, ok := PackageRepository(pkg); !ok || strings.ContainsAny(pkg, " \t\n@") { return fmt.Errorf("delivery: the import %q is no mesh package's path (it starts with %s)", pkg, ForgeModulePrefix) } if i > 0 && imports[i-1] >= pkg { return fmt.Errorf("delivery: the imports are not sorted and each once (%s after %s)", pkg, imports[i-1]) } } return nil }