Let a verdict warn of what a stricter judge found in a manifest it accepted before (ADR 0579 §1)

Issue 1585: ManifestVerdict and the modules value carry Warnings; the finding manifest-outgrown names it.

Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
This commit is contained in:
2026-11-01 02:43:10 +01:00
parent a20bdf7952
commit 19c204273b
3 changed files with 36 additions and 14 deletions
+9 -1
View File
@@ -431,13 +431,19 @@ type Module struct {
// that state. Empty in a value written before the indexer judged, which a reader takes as accepted (TakeManifestVerdict).
Verdict string `json:"verdict,omitempty"`
Problems []string `json:"problems,omitempty"`
// Warnings are the problems of an accepted manifest a stricter judge outgrew (novox/hq ADR 0579 §1): Verdict is then
// empty (accepted), and the watchman raises delivery.ManifestOutgrown until the manifest is mended.
Warnings []string `json:"warnings,omitempty"`
}
// VerdictOf is the verdict a modules value carries, or nil when it carries none (taken as accepted, TakeManifestVerdict).
func (m Module) VerdictOf(module string) *ManifestVerdict {
if m.Verdict == "" {
if m.Verdict == "" && len(m.Warnings) == 0 {
return nil
}
if m.Verdict == "" {
return &ManifestVerdict{Module: module, State: VerdictAccepted, Warnings: m.Warnings}
}
return &ManifestVerdict{Module: module, State: m.Verdict, Problems: m.Problems}
}
@@ -554,6 +560,8 @@ func (m Module) check() error {
return fmt.Errorf("delivery: a module carries a verdict and no manifest digest")
case (m.Verdict == VerdictRefused) != (len(m.Problems) > 0):
return fmt.Errorf("delivery: a module's problems are named only with a refusal, and a refusal names them")
case m.Verdict == VerdictRefused && len(m.Warnings) > 0:
return fmt.Errorf("delivery: a refused module warns: a refusal names problems")
}
return checkNewForm(m.FP2, m.Manifest)
}
+17 -13
View File
@@ -3,6 +3,7 @@ package delivery
import (
"encoding/json"
"fmt"
"slices"
"time"
)
@@ -26,15 +27,23 @@ const (
// 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"`
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"`
}
@@ -45,15 +54,7 @@ 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 {
if v.Module != o.Module || v.State != o.State || len(v.Problems) != len(o.Problems) {
return false
}
for i := range v.Problems {
if v.Problems[i] != o.Problems[i] {
return false
}
}
return true
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 {
@@ -69,6 +70,9 @@ func (v ManifestVerdict) check() error {
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)
}
+10
View File
@@ -28,6 +28,7 @@ func TestAManifestVerdictIsTheContractReadersTake(t *testing.T) {
"another state": {Module: "m", State: "maybe", Judge: "j"},
"refused, no problem": {Module: "m", State: VerdictRefused, Judge: "j"},
"accepted, a problem": {Module: "m", State: VerdictAccepted, Problems: []string{"x"}, Judge: "j"},
"refused, a warning": {Module: "m", State: VerdictRefused, Problems: []string{"x"}, Warnings: []string{"y"}, Judge: "j"},
} {
if _, err := EncodeManifestVerdict(bad); err == nil {
t.Errorf("%s: encoded", name)
@@ -62,4 +63,13 @@ func TestAManifestVerdictIsTheContractReadersTake(t *testing.T) {
if _, err := EncodeModule(m); err == nil {
t.Fatal("a module refused naming no problem was encoded")
}
// ADR 0579 §1: an accepted manifest a stricter judge outgrew carries its warnings and is taken as accepted.
outgrown := ManifestVerdict{Module: "x", State: VerdictAccepted, Warnings: []string{"w"}, Judge: "j"}
if _, err := EncodeManifestVerdict(outgrown); err != nil || outgrown.Same(ManifestVerdict{Module: "x", State: VerdictAccepted}) {
t.Fatalf("an outgrown verdict: %v", err)
}
m.Verdict, m.Warnings = "", []string{"w"}
if r, _ := TakeManifestVerdict(m.VerdictOf("x")); r || len(m.VerdictOf("x").Warnings) != 1 {
t.Fatalf("an outgrown module: %+v", m.VerdictOf("x"))
}
}