Give a module and a candidate the new-form fingerprint, the manifest digest and the fpset, in version 1 (issue 1357)
This commit is contained in:
@@ -30,6 +30,10 @@
|
||||
// **The source fingerprint** (issue 1203; ADR 0437 §5): one function of named inputs that the controller's builder and
|
||||
// the indexer both call, so they agree byte for byte (fingerprint.go).
|
||||
//
|
||||
// **The release 4 layouts** (issues 1356 and 1357; ADR 0453 clauses 6 and 16): the new fingerprint form (fingerprint2.go),
|
||||
// and the indexer's manifests by digest and the new fields of its modules and candidates (manifests.go, source.go). A
|
||||
// manifests value is the one exception to the size above: a manifest is up to about 21 KB, whole, byte for byte.
|
||||
//
|
||||
// Standard library only, and this module's own asks package.
|
||||
package delivery
|
||||
|
||||
|
||||
+107
-4
@@ -31,6 +31,10 @@ const (
|
||||
ModulesBucket = IndexerModule + "_modules"
|
||||
// ModulesHistory is the revisions the modules bucket keeps per key.
|
||||
ModulesHistory = 20
|
||||
// ManifestsBucket is the indexer's bucket of manifests by digest (novox/hq ADR 0429 clause 16); see manifests.go.
|
||||
ManifestsBucket = IndexerModule + "_manifests"
|
||||
// ManifestsHistory is the revisions the manifests bucket keeps per key: a key is written once.
|
||||
ManifestsHistory = 1
|
||||
)
|
||||
|
||||
// ForgeStream is the forge bridge's stream as its manifest declares it; the broker derives the real names.
|
||||
@@ -245,19 +249,86 @@ func (s Source) check() error {
|
||||
|
||||
// Candidate is the value of a candidates key: the group's pull requests and heads, the heads hash, the base (the
|
||||
// source revision it was previewed onto) and the modules each pull request touches. It carries no fingerprint
|
||||
// until release 4 (ADR 0437 §5).
|
||||
// until release 4 (ADR 0437 §5); from R4c the indexer writes the new form's fingerprint of each touched module in
|
||||
// the pull request's Fps and, over all of them, FPSet (novox/hq ADR 0453 clauses 6 and 16). Both are absent in a
|
||||
// value written before, and a reader of today's shape ignores them.
|
||||
type Candidate struct {
|
||||
V int `json:"v"`
|
||||
Group string `json:"group"`
|
||||
Base uint64 `json:"base"`
|
||||
HeadsHash string `json:"heads"`
|
||||
Pulls []CandidatePull `json:"pulls"`
|
||||
FPSet string `json:"fpset,omitempty"`
|
||||
}
|
||||
|
||||
// CandidatePull is one pull request of a candidate and the modules it touches.
|
||||
// CandidatePull is one pull request of a candidate and the modules it touches. Fps is, by module name, the new
|
||||
// form's fingerprint of the module at the merge preview, or why it has none, and the digest of its manifest (the key
|
||||
// of the indexer's manifests bucket); a module of Modules without an entry has none written yet.
|
||||
type CandidatePull struct {
|
||||
Head
|
||||
Modules []string `json:"modules"`
|
||||
Modules []string `json:"modules"`
|
||||
Fps map[string]CandidateModule `json:"fps,omitempty"`
|
||||
}
|
||||
|
||||
// CandidateModule is one touched module's new-form fingerprint FP2 (`src2:` and 64 hex digits), or None2, the reason
|
||||
// it has none; exactly one of them. Manifest is the digest of the module's manifest as read at the preview.
|
||||
type CandidateModule struct {
|
||||
FP2 string `json:"fp2,omitempty"`
|
||||
None2 string `json:"none2,omitempty"`
|
||||
Manifest string `json:"manifest,omitempty"`
|
||||
}
|
||||
|
||||
func (m CandidateModule) check() error {
|
||||
switch {
|
||||
case m.FP2 != "" && m.None2 != "":
|
||||
return fmt.Errorf("delivery: a module has both fp2 and none2")
|
||||
case m.FP2 == "" && m.None2 == "":
|
||||
return fmt.Errorf("delivery: a module has neither fp2 nor none2")
|
||||
}
|
||||
return checkNewForm(m.FP2, m.Manifest)
|
||||
}
|
||||
|
||||
// checkNewForm refuses an fp2 that is not of the new form and a manifest digest that is not a digest, naming it.
|
||||
func checkNewForm(fp2, manifest string) error {
|
||||
if fp2 != "" {
|
||||
if _, err := FingerprintKeyPart(fp2); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if manifest != "" {
|
||||
if _, err := ManifestsKey(manifest); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// FingerprintSet is the fpset of a candidate's pull requests: the first 12 hex digits of the sha256 over the lines
|
||||
// `<module>=<fp2>` or `<module>=none:<reason>` of every module with an entry in Fps, sorted and joined by a newline,
|
||||
// so it is the same for every order and changes with any fingerprint or reason. A manifest digest does not enter: the
|
||||
// fingerprint already covers the manifest. It is the empty string when no pull request carries a fingerprint. The
|
||||
// checker keys its verdict by it (research 063 section 01).
|
||||
func FingerprintSet(pulls []CandidatePull) string {
|
||||
lines := map[string]bool{}
|
||||
for _, p := range pulls {
|
||||
for module, m := range p.Fps {
|
||||
if m.FP2 != "" {
|
||||
lines[module+"="+m.FP2] = true
|
||||
} else {
|
||||
lines[module+"=none:"+m.None2] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(lines) == 0 {
|
||||
return ""
|
||||
}
|
||||
all := make([]string, 0, len(lines))
|
||||
for l := range lines {
|
||||
all = append(all, l)
|
||||
}
|
||||
sort.Strings(all)
|
||||
sum := sha256.Sum256([]byte(strings.Join(all, "\n")))
|
||||
return hex.EncodeToString(sum[:])[:12]
|
||||
}
|
||||
|
||||
func (c Candidate) check() error {
|
||||
@@ -272,16 +343,46 @@ func (c Candidate) check() error {
|
||||
if c.HeadsHash != want {
|
||||
return fmt.Errorf("delivery: the candidate's heads hash %q is not the hash %q of its pull requests' heads", c.HeadsHash, want)
|
||||
}
|
||||
seen := map[string]CandidateModule{}
|
||||
for _, p := range c.Pulls {
|
||||
touched := map[string]bool{}
|
||||
for _, m := range p.Modules {
|
||||
touched[m] = true
|
||||
}
|
||||
for module, m := range p.Fps {
|
||||
if !touched[module] {
|
||||
return fmt.Errorf("delivery: the candidate gives a fingerprint of the module %q, which pull request %s#%d does not touch", module, p.Repository, p.Number)
|
||||
}
|
||||
if err := m.check(); err != nil {
|
||||
return fmt.Errorf("delivery: the candidate's module %q: %w", module, err)
|
||||
}
|
||||
if before, ok := seen[module]; ok && before != m {
|
||||
return fmt.Errorf("delivery: two pull requests of the candidate give the module %q different fingerprints", module)
|
||||
}
|
||||
seen[module] = m
|
||||
}
|
||||
}
|
||||
if wantSet := FingerprintSet(c.Pulls); c.FPSet != wantSet {
|
||||
return fmt.Errorf("delivery: the candidate's fpset %q is not the hash %q of its fingerprints", c.FPSet, wantSet)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Module is the value of a modules key: the module's source fingerprint, or none and why (a build that resolves
|
||||
// packages when built gets none, said here), and where it was computed: Repository and Commit are the trunk merge,
|
||||
// Src the source revision then (0 before the gate writes source.main). Exactly one of FP and None is set.
|
||||
//
|
||||
// From R4c the indexer writes the new form beside it (novox/hq ADR 0453 clause 6): FP2, the fingerprint of the new
|
||||
// form (`src2:` and 64 hex digits), or None2, why it has none; one of them whenever either is, never both; and
|
||||
// Manifest, the digest of the module's manifest, the key of the manifests bucket. A value written before has none of
|
||||
// the three, and a reader of today's shape ignores them.
|
||||
type Module struct {
|
||||
V int `json:"v"`
|
||||
FP string `json:"fp,omitempty"`
|
||||
None string `json:"none,omitempty"`
|
||||
FP2 string `json:"fp2,omitempty"`
|
||||
None2 string `json:"none2,omitempty"`
|
||||
Manifest string `json:"manifest,omitempty"`
|
||||
Src uint64 `json:"src,omitempty"`
|
||||
Repository string `json:"repository"`
|
||||
Commit string `json:"commit"`
|
||||
@@ -295,8 +396,10 @@ func (m Module) check() error {
|
||||
return fmt.Errorf("delivery: a module has neither a fingerprint nor a reason for none")
|
||||
case m.FP != "" && !strings.HasPrefix(m.FP, SourcePrefix):
|
||||
return fmt.Errorf("delivery: the module's fingerprint %q does not start with %s", m.FP, SourcePrefix)
|
||||
case m.FP2 != "" && m.None2 != "":
|
||||
return fmt.Errorf("delivery: a module has both fp2 and none2")
|
||||
}
|
||||
return nil
|
||||
return checkNewForm(m.FP2, m.Manifest)
|
||||
}
|
||||
|
||||
// Forge is a message of the forge bridge's stream, trimmed to repository, branch, pull request and head. A pull
|
||||
|
||||
Reference in New Issue
Block a user