From 0364fcccc00601e73d0eb4f281b1adfbbc44b60d Mon Sep 17 00:00:00 2001 From: jochen Date: Fri, 6 Nov 2026 22:07:41 +0100 Subject: [PATCH] Keep a composer request's seats through the decode, the fill and the marshal (issue 1921) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A build module that fills a request re-marshals it, and without the field it dropped the seat list the trunk's manifest validator reads, so the validator never got one (milestone 47/B0, ADR 0636 §4, ADR 0644). Claude-Session: https://claude.ai/code/session_01NcbgfFSnn24M3GcqttoU7i --- go/composer/composer.go | 5 +++++ go/composer/composer_test.go | 8 +++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/go/composer/composer.go b/go/composer/composer.go index dd224cf..183f79f 100644 --- a/go/composer/composer.go +++ b/go/composer/composer.go @@ -59,6 +59,11 @@ type Request struct { // longer read: the composer judges no manifest (novox/hq ADR 0578 §5), the indexer's judge does, and its verdicts // come in each case's Verdicts. Kept so an asker that still sends it is read. Predefined catalogue.PredefinedList `json:"predefined,omitempty"` + // Seats are the seat definitions the manifest validator judges and the composer composes against, raw so that this + // package names no seat: inside a group's check, the one seat list the checking program made from the members' heads + // (novox/hq ADR 0636 §4, ADR 0644). A reader built before them ignores them; Decode, Fill and the request's + // marshalling keep them whole, so a build module that fills a request hands them on (issue 1921). + Seats []json.RawMessage `json:"seats,omitempty"` } // Case is one catalogue composed: every module's manifest by name, and per node the manifests kept at the build it runs. diff --git a/go/composer/composer_test.go b/go/composer/composer_test.go index 108e3c7..a94da08 100644 --- a/go/composer/composer_test.go +++ b/go/composer/composer_test.go @@ -12,7 +12,9 @@ const demo = `{"module": "demo", "version": "1", "claims": [{"name": "node-files // A manifest named by its digest goes on the wire as {"digest": ...}, and Fill puts the bytes the indexer keeps in its // place; a raw manifest goes as itself. func TestAManifestIsItsBytesOrItsDigest(t *testing.T) { - r := Request{V: Version, Cases: []Case{{Name: "change", Manifests: map[string]Manifest{ + // 1921 (B0): the seat list rides whole through the wire, the decode and the fill. + seats := []json.RawMessage{json.RawMessage(`{"name":"node-files","verbs":["read"]}`)} + r := Request{V: Version, Seats: seats, Cases: []Case{{Name: "change", Manifests: map[string]Manifest{ "demo": {Digest: "d1"}, "raw": {Raw: json.RawMessage(`{"module":"raw","version":"1"}`)}}, Kept: map[string]map[string]Manifest{"laptop": {"demo": {Digest: "d1"}}}}}} wire, err := json.Marshal(r) @@ -36,6 +38,10 @@ func TestAManifestIsItsBytesOrItsDigest(t *testing.T) { }); err != nil { t.Fatal(err) } + refilled, err := json.Marshal(back) + if err != nil || !strings.Contains(string(refilled), `"seats":[{"name":"node-files","verbs":["read"]}]`) { + t.Fatalf("the seats after the fill: %s %v", refilled, err) + } if asked != 2 || string(back.Cases[0].Manifests["demo"].Raw) != demo || string(back.Cases[0].Kept["laptop"]["demo"].Raw) != demo { t.Fatalf("filled %d times: %+v", asked, back.Cases[0]) }