Issue 1585: ManifestVerdict and the modules value carry Warnings; the finding manifest-outgrown names it. Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
157 lines
6.4 KiB
Go
157 lines
6.4 KiB
Go
package delivery
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"slices"
|
|
"time"
|
|
)
|
|
|
|
// The verdicts on manifests (novox/hq ADR 0578 §1, §3): the indexer judges every manifest it takes in once, with its own
|
|
// judge, and writes the verdict in its own state; every other part decodes a manifest leniently (catalogue.Decode) and
|
|
// takes the verdict from here. This is the contract only: what a verdict holds, its key and its event. The rules that
|
|
// make a verdict are the judge's, a package of mesh-delivery the indexer and the checker use, and no reader runs them.
|
|
|
|
const (
|
|
// VerdictsBucket is the indexer's state of verdicts, one key per manifest digest (VerdictsKey).
|
|
VerdictsBucket = IndexerModule + "_verdicts"
|
|
// VerdictsHistory is the revisions the verdicts state keeps per key: a verdict is written again only when the judge
|
|
// that made it changed and the verdict with it.
|
|
VerdictsHistory = 5
|
|
|
|
// VerdictAccepted is the state of a manifest the judge found no problem in.
|
|
VerdictAccepted = "accepted"
|
|
// VerdictRefused is the state of a manifest the judge found a problem in; Problems says each.
|
|
VerdictRefused = "refused"
|
|
|
|
// ManifestRefusedEvent is the indexer's event saying a manifest was refused (ManifestRefused), so the watchman
|
|
// raises it as a condition beside the indexer's finding.
|
|
ManifestRefusedEvent = "manifest-refused"
|
|
|
|
// ManifestOutgrown is the finding of a manifest the indexer accepted before and a stricter judge finds problems in
|
|
// (novox/hq ADR 0579 §1): it stays accepted, its problems are the verdict's Warnings, and the watchman raises it as a
|
|
// warning until the manifest is mended.
|
|
ManifestOutgrown = "manifest-outgrown"
|
|
)
|
|
|
|
// ManifestVerdict is the value of a verdicts key: the module whose manifest it is, accepted or refused, every problem the judge
|
|
// found (none when accepted), the judge that made it (the indexer's build fingerprint) and when.
|
|
type ManifestVerdict struct {
|
|
V int `json:"v"`
|
|
Module string `json:"module"`
|
|
State string `json:"state"`
|
|
Problems []string `json:"problems,omitempty"`
|
|
// Warnings are the problems a stricter judge found in a manifest it accepted before, digest unchanged (novox/hq ADR
|
|
// 0579 §1): the verdict stays accepted.
|
|
Warnings []string `json:"warnings,omitempty"`
|
|
Judge string `json:"judge"`
|
|
At time.Time `json:"at"`
|
|
}
|
|
|
|
// Refused is whether v refuses its manifest.
|
|
func (v ManifestVerdict) Refused() bool { return v.State == VerdictRefused }
|
|
|
|
// Same is whether v and o say the same of a manifest: the module, the state and the problems, whoever judged and when.
|
|
// The indexer writes a verdict only when it is not the same as the one it holds.
|
|
func (v ManifestVerdict) Same(o ManifestVerdict) bool {
|
|
return v.Module == o.Module && v.State == o.State && slices.Equal(v.Problems, o.Problems) && slices.Equal(v.Warnings, o.Warnings)
|
|
}
|
|
|
|
func (v ManifestVerdict) check() error {
|
|
if err := checkName("module", v.Module); err != nil {
|
|
return err
|
|
}
|
|
switch v.State {
|
|
case VerdictAccepted:
|
|
if len(v.Problems) > 0 {
|
|
return fmt.Errorf("delivery: the verdict on %s accepts its manifest and names problems", v.Module)
|
|
}
|
|
case VerdictRefused:
|
|
if len(v.Problems) == 0 {
|
|
return fmt.Errorf("delivery: the verdict on %s refuses its manifest and names no problem", v.Module)
|
|
}
|
|
if len(v.Warnings) > 0 {
|
|
return fmt.Errorf("delivery: the verdict on %s refuses its manifest and warns: a refusal names problems", v.Module)
|
|
}
|
|
default:
|
|
return fmt.Errorf("delivery: the verdict on %s is %q, neither %s nor %s", v.Module, v.State, VerdictAccepted, VerdictRefused)
|
|
}
|
|
if v.Judge == "" {
|
|
return fmt.Errorf("delivery: the verdict on %s names no judge", v.Module)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// VerdictsKey is the key, in VerdictsBucket, of the verdict on a manifest: its digest (ManifestDigest), as in the
|
|
// manifests bucket.
|
|
func VerdictsKey(digest string) (string, error) {
|
|
if !isHex64(digest) {
|
|
return "", fmt.Errorf("delivery: %q is not a manifest digest, 64 lowercase hex digits", digest)
|
|
}
|
|
return digest, nil
|
|
}
|
|
|
|
// EncodeManifestVerdict is v as JSON at Version; it refuses a verdict without a module or a judge, a state other than accepted
|
|
// or refused, a refusal naming no problem and an acceptance naming one.
|
|
func EncodeManifestVerdict(v ManifestVerdict) ([]byte, error) {
|
|
if err := v.check(); err != nil {
|
|
return nil, err
|
|
}
|
|
v.V = Version
|
|
return json.Marshal(v)
|
|
}
|
|
|
|
// DecodeManifestVerdict reads a verdict; it refuses an unknown version (ErrUnknownVersion) and what EncodeManifestVerdict refuses.
|
|
func DecodeManifestVerdict(data []byte) (ManifestVerdict, error) {
|
|
var v ManifestVerdict
|
|
if err := decode(data, &v); err != nil {
|
|
return ManifestVerdict{}, err
|
|
}
|
|
if err := v.check(); err != nil {
|
|
return ManifestVerdict{}, err
|
|
}
|
|
return v, nil
|
|
}
|
|
|
|
// TakeManifestVerdict is how a reader takes the verdict on a manifest (ADR 0578 §3): refused, with its problems, only when the
|
|
// indexer wrote a refusal. A missing verdict (nil) is taken as accepted, because every manifest the indexer holds before
|
|
// it has judged it was taken in under the judges that ran until then; that lasts until the indexer's first pass after its
|
|
// delivery has written every verdict. A reader holds a refused module and names it with Why, and reads the rest.
|
|
func TakeManifestVerdict(v *ManifestVerdict) (refused bool, problems []string) {
|
|
if v == nil || !v.Refused() {
|
|
return false, nil
|
|
}
|
|
return true, v.Problems
|
|
}
|
|
|
|
// Why is a refusal in a reader's words: the module, that the indexer's judge refused its manifest, and each problem.
|
|
func (v ManifestVerdict) Why() string {
|
|
if !v.Refused() {
|
|
return ""
|
|
}
|
|
judge := "the indexer's judge"
|
|
if v.Judge != "" {
|
|
judge += " (" + v.Judge + ")"
|
|
}
|
|
why := fmt.Sprintf("%s refused the manifest of %s:", judge, v.Module)
|
|
for _, p := range v.Problems {
|
|
why += " " + p + ";"
|
|
}
|
|
return why[:len(why)-1]
|
|
}
|
|
|
|
// ManifestRefused is the body of the indexer's event ManifestRefusedEvent: the module, the digest of its manifest, the
|
|
// judge and each problem, as its verdict holds them.
|
|
type ManifestRefused struct {
|
|
V int `json:"v"`
|
|
Module string `json:"module"`
|
|
Digest string `json:"digest"`
|
|
Judge string `json:"judge"`
|
|
Problems []string `json:"problems"`
|
|
}
|
|
|
|
// RefusedOf is the event body saying v, the verdict on the manifest of digest.
|
|
func RefusedOf(digest string, v ManifestVerdict) ManifestRefused {
|
|
return ManifestRefused{V: Version, Module: v.Module, Digest: digest, Judge: v.Judge, Problems: v.Problems}
|
|
}
|