From 19c204273bdc6565c5bc0e9bf8c8f767c135c0d8 Mon Sep 17 00:00:00 2001 From: jochen Date: Sun, 1 Nov 2026 02:43:10 +0100 Subject: [PATCH] =?UTF-8?q?Let=20a=20verdict=20warn=20of=20what=20a=20stri?= =?UTF-8?q?cter=20judge=20found=20in=20a=20manifest=20it=20accepted=20befo?= =?UTF-8?q?re=20(ADR=200579=20=C2=A71)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Issue 1585: ManifestVerdict and the modules value carry Warnings; the finding manifest-outgrown names it. Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL --- go/delivery/source.go | 10 +++++++++- go/delivery/verdicts.go | 30 +++++++++++++++++------------- go/delivery/verdicts_test.go | 10 ++++++++++ 3 files changed, 36 insertions(+), 14 deletions(-) diff --git a/go/delivery/source.go b/go/delivery/source.go index 33595a7..a627671 100644 --- a/go/delivery/source.go +++ b/go/delivery/source.go @@ -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) } diff --git a/go/delivery/verdicts.go b/go/delivery/verdicts.go index 86de134..15a6a9c 100644 --- a/go/delivery/verdicts.go +++ b/go/delivery/verdicts.go @@ -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) } diff --git a/go/delivery/verdicts_test.go b/go/delivery/verdicts_test.go index 0de3517..da05519 100644 --- a/go/delivery/verdicts_test.go +++ b/go/delivery/verdicts_test.go @@ -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")) + } }