From 6bce1fb0d2a12795e1e4201fede314a632e6a170 Mon Sep 17 00:00:00 2001 From: jochen Date: Sat, 31 Oct 2026 00:23:42 +0100 Subject: [PATCH] Add the node-engine verb composition to the seat's contract, and the resources two compositions differ in (issue 774) A provider waiting for a yes said why only in its have's words; nobody could read the composition that waits beside what runs. The engine's read verb composition answers that, and ChangedResources lists what a move changes, a received file naming the consumers that join or leave. --- go/catalogue/engine_seat.go | 12 ++++++++++++ go/catalogue/seats_test.go | 2 +- go/delivery/judge.go | 37 +++++++++++++++++++++++++++++++++++++ go/delivery/judge_test.go | 10 ++++++++++ 4 files changed, 60 insertions(+), 1 deletion(-) diff --git a/go/catalogue/engine_seat.go b/go/catalogue/engine_seat.go index b924506..a10e9ec 100644 --- a/go/catalogue/engine_seat.go +++ b/go/catalogue/engine_seat.go @@ -49,6 +49,18 @@ func EngineVerbs() []Verb { "hands its lease away, beside the controller's mirror for this machine and their difference. With write, the " + "key written again from what the engine composed.", Input: schema(map[string]string{"write": "\"true\": write this machine's key again from what the engine composed"}, nil, "write")}, + // Read-only (issue 774): why a module waits for a yes, or what its next apply would change. Optional until every + // engine serves it. + {Name: "composition", Optional: true, Description: "One module's composition on this machine beside what runs of " + + "it, read-only: the composition its want's build has in the bucket composed- (the latest revision, or the " + + "one whose digest is given, from the revisions the bucket keeps), the digest of each kept revision, what runs " + + "(applied, its have, or the kept declaration), its want and have (phase running, failed, held or waiting-yes, " + + "and the digest that waits for a yes), and the move's judgement with its words: why a record module waits for " + + "a yes. With diff, every resource added, removed or changed, a provider's received file naming the consumers " + + "that join or leave.", + Input: schema(map[string]string{"module": "the module, as placed on this machine; node for the node's own", + "digest": "a composition's digest (sha256:…), to read that revision rather than the latest", + "diff": "\"true\": list the resources that differ from what runs"}, []string{"module"})}, } } diff --git a/go/catalogue/seats_test.go b/go/catalogue/seats_test.go index e6b206b..d18d35f 100644 --- a/go/catalogue/seats_test.go +++ b/go/catalogue/seats_test.go @@ -98,7 +98,7 @@ func TestTheSeatsAreAClosedSetAndEachNamesItsDecision(t *testing.T) { if props, _ := engine.Serves[5].Input["properties"].(map[string]any); props["id"] == nil { t.Errorf("acts takes no id: %v", engine.Serves[5].Input) } - if strings.Join(verbs, ",") != "hand-over,access-act,judge-directories,setuid-search,root-free,acts,packages" || + if strings.Join(verbs, ",") != "hand-over,access-act,judge-directories,setuid-search,root-free,acts,packages,composition" || strings.Join(engine.Emits, ",") != "asked,done,refused" { t.Errorf("node-engine serves %v and emits %v", verbs, engine.Emits) } diff --git a/go/delivery/judge.go b/go/delivery/judge.go index 7da4d6c..a060fc7 100644 --- a/go/delivery/judge.go +++ b/go/delivery/judge.go @@ -139,6 +139,43 @@ func judged(from, to Composed) (Judgement, string) { return JudgedNeither, "it recreates no container and moves no data" } +// ResourceChange is one resource that differs between two compositions of a module: its id, its kind, whether it is +// added, removed or changed, and for a file the mesh makes for a provision (received-, bound-, secret-) the consumers +// that join or leave, in words. +type ResourceChange struct { + ID string `json:"id"` + Kind string `json:"kind"` + Change string `json:"change"` + Consumers []string `json:"consumers,omitempty"` +} + +// ChangedResources is every resource added, removed or changed from running to composed, by id, each compared +// canonical (issue 774: the engine's verb composition shows what a move waiting for a yes would change). Running nil +// is every resource added. +func ChangedResources(running *Composed, composed Composed) []ResourceChange { + var from Composed + if running != nil { + from = *running + } + before, after := judgedResourcesByID(from), judgedResourcesByID(composed) + var out []ResourceChange + for _, id := range changedOf(before, after, func(string) bool { return true }) { + was, now := before[id], after[id] + c := ResourceChange{ID: id, Change: "changed"} + switch { + case was == nil: + c.Change, c.Kind = "added", now.kind + case now == nil: + c.Change, c.Kind = "removed", was.kind + default: + c.Kind = now.kind + } + c.Consumers = consumersOf(id, was, now) + out = append(out, c) + } + return out +} + // consumersOf is, for a changed file the mesh makes for a provision (received-, bound- or secret-, catalogue.ReceivedID, // BoundID, SecretID), what changes of it in words: each consumer that joins or leaves a received file, by the given // list's from and node; else that what it is given for the provision changes. Nil for any other resource. diff --git a/go/delivery/judge_test.go b/go/delivery/judge_test.go index 39545ec..ad17be4 100644 --- a/go/delivery/judge_test.go +++ b/go/delivery/judge_test.go @@ -2,6 +2,7 @@ package delivery import ( "encoding/json" + "fmt" "strings" "testing" ) @@ -78,6 +79,15 @@ func TestJudgeMove(t *testing.T) { t.Errorf("%s: the flag's difference is not said: %q", c.name, got.Words) } } + // The resources that differ, as the engine's verb composition lists them (issue 774): a provider's received file + // names its consumers, a file changed and one added are said as such, and nothing equal is listed. + changes := ChangedResources(mail('a', true, container, spool, received, config), *mail('b', true, container, spool, received2, config2, tools)) + if want := `[{mesh-mail.conf file changed []} {mesh-mail.received-smtp file changed [cloud on novox leaves smtp wiki on ace joins smtp]} {mesh-mail.tools file added []}]`; fmt.Sprint(changes) != want { + t.Errorf("changed resources: %v, want %s", changes, want) + } + if got := ChangedResources(nil, *mail('b', true, spool)); len(got) != 1 || got[0].Change != "added" { + t.Errorf("never ran: %v, want its one resource added", got) + } if got := JudgeMove(nil, Composed{Node: "ace"}); got.Judgement != JudgedNeither { t.Errorf("the node's own composition: %+v", got) }