From e089fd9c3c220ad7816b98c7e8da53a232c9142a Mon Sep 17 00:00:00 2001 From: jochen Date: Wed, 11 Nov 2026 22:48:51 +0100 Subject: [PATCH] indexer, mesh-catalog contract: manifest-accepted carries problems, the validator's strings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Builds: design 47 G §692, §668 Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h --- contracts/go/seats/meshcatalog/contract.go | 2 ++ contracts/seats/mesh-catalog/seat.json | 10 +++++++- contracts/ts/seats/meshcatalog/index.d.ts | 2 ++ .../internal/index/accepted_problems_test.go | 25 +++++++++++++++++++ .../internal/index/events.go | 4 ++- 5 files changed, 41 insertions(+), 2 deletions(-) create mode 100644 modules/mesh-delivery-indexer/internal/index/accepted_problems_test.go diff --git a/contracts/go/seats/meshcatalog/contract.go b/contracts/go/seats/meshcatalog/contract.go index 9b77bf06..34b65f16 100644 --- a/contracts/go/seats/meshcatalog/contract.go +++ b/contracts/go/seats/meshcatalog/contract.go @@ -1428,6 +1428,8 @@ type ManifestAcceptedEvent struct { Digest string `json:"digest"` Judge string `json:"judge"` Module string `json:"module"` + // each problem the indexer's re-judgement finds in a manifest it keeps accepted because its digest is unchanged: the manifest validator's strings, the same items its verdict writes as warnings, in the same order; an empty list when none (design 47 G section 668, 692) + Problems []string `json:"problems"` // the indexer's revision of the verdict it says; a reader asks its first view again on a gap in it; zero from an indexer that does not say it Revision int64 `json:"revision,omitempty"` V int64 `json:"v"` diff --git a/contracts/seats/mesh-catalog/seat.json b/contracts/seats/mesh-catalog/seat.json index 7eaf9bc7..40fba93d 100644 --- a/contracts/seats/mesh-catalog/seat.json +++ b/contracts/seats/mesh-catalog/seat.json @@ -678,6 +678,13 @@ "type": "integer", "description": "the indexer's revision of the verdict it says; a reader asks its first view again on a gap in it; zero from an indexer that does not say it" }, + "problems": { + "type": "array", + "items": { + "type": "string" + }, + "description": "each problem the indexer's re-judgement finds in a manifest it keeps accepted because its digest is unchanged: the manifest validator's strings, the same items its verdict writes as warnings, in the same order; an empty list when none (design 47 G section 668, 692)" + }, "bus-state": { "type": "array", "items": { @@ -717,7 +724,8 @@ "module", "digest", "judge", - "bus-state" + "bus-state", + "problems" ] } }, diff --git a/contracts/ts/seats/meshcatalog/index.d.ts b/contracts/ts/seats/meshcatalog/index.d.ts index 795018b6..dc83114e 100644 --- a/contracts/ts/seats/meshcatalog/index.d.ts +++ b/contracts/ts/seats/meshcatalog/index.d.ts @@ -878,6 +878,8 @@ export interface ManifestAcceptedEvent { "digest": string; "judge": string; "module": string; + /** each problem the indexer's re-judgement finds in a manifest it keeps accepted because its digest is unchanged: the manifest validator's strings, the same items its verdict writes as warnings, in the same order; an empty list when none (design 47 G section 668, 692) */ + "problems": string[]; /** the indexer's revision of the verdict it says; a reader asks its first view again on a gap in it; zero from an indexer that does not say it */ "revision"?: number; "v": number; diff --git a/modules/mesh-delivery-indexer/internal/index/accepted_problems_test.go b/modules/mesh-delivery-indexer/internal/index/accepted_problems_test.go new file mode 100644 index 00000000..c27764d7 --- /dev/null +++ b/modules/mesh-delivery-indexer/internal/index/accepted_problems_test.go @@ -0,0 +1,25 @@ +package index + +import ( + "encoding/json" + "reflect" + "strings" + "testing" + + "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog" +) + +// Design 47 G §668, §692: manifest-accepted's problems are the validator's strings, the same the verdict writes as +// warnings, in the same order; a mended manifest says an empty list, never null. +func TestManifestAcceptedProblemsAreTheVerdictsWarnings(t *testing.T) { + warnings := []string{`requires "amqp", which no module provides`, "a second problem"} + got := acceptedOf("sha256:aa", meshcatalog.VerdictsByDigestValue{Module: "m", Judge: "j", Warnings: warnings}, 3, []byte(`{"module":"m","version":"1.0"}`)) + if !reflect.DeepEqual(got.Problems, warnings) { + t.Errorf("problems %v, want the verdict's warnings %v", got.Problems, warnings) + } + mended := acceptedOf("sha256:bb", meshcatalog.VerdictsByDigestValue{Module: "m", Judge: "j"}, 4, []byte(`{"module":"m","version":"2.0"}`)) + raw, _ := json.Marshal(mended) + if !strings.Contains(string(raw), `"problems":[]`) { + t.Errorf("a mended manifest's event is %s, want an empty problems list", raw) + } +} diff --git a/modules/mesh-delivery-indexer/internal/index/events.go b/modules/mesh-delivery-indexer/internal/index/events.go index 5eb02b1f..9a27e1ea 100644 --- a/modules/mesh-delivery-indexer/internal/index/events.go +++ b/modules/mesh-delivery-indexer/internal/index/events.go @@ -20,7 +20,9 @@ func refusedOf(digest string, v meshcatalog.VerdictsByDigestValue) meshcatalog.M // acceptedEvent is the body of manifest-accepted for the verdict v accepting the manifest m of digest, its bus-state // entries whole (novox/hq ADR 0595 §4); an empty list when m asks for none. func acceptedEvent(digest string, v meshcatalog.VerdictsByDigestValue, m catalogue.Manifest) meshcatalog.ManifestAcceptedEvent { - return meshcatalog.ManifestAcceptedEvent{V: keys.Version, Module: v.Module, Digest: digest, Judge: v.Judge, BusState: busStateOf(m.BusState)} + // problems are the manifest validator's strings, the verdict's warnings in their order, an empty list when none (design 47 G §668, §692). + problems := append([]string{}, v.Warnings...) + return meshcatalog.ManifestAcceptedEvent{V: keys.Version, Module: v.Module, Digest: digest, Judge: v.Judge, BusState: busStateOf(m.BusState), Problems: problems} } // busStateOf is the manifest's bus-state entries in the event's form (the same JSON).