From 42ac1a9a0aaa6410c7ca061e4201404e86b0af0c Mon Sep 17 00:00:00 2001 From: jochen Date: Wed, 4 Nov 2026 12:38:10 +0100 Subject: [PATCH] mesh-sdk's node-engine names are the seat's generated contract, kept deprecated for their importers, with their checks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The seat is defined whole in mesh-contracts (ADR 0613 §3), so mesh-sdk takes its names from there (ADR 0613 §5): EngineSeat and EngineEvents are nodeengine's, EngineComposeCandidateVerb is nodeengine.VerbComposeCandidate, and ComposeCandidateAsk, ComposeCandidateAnswer and ModuleComposed are aliases of nodeengine's ComposeCandidateInput, ComposeCandidateAnswer and Module. The encode and decode checks stay here over those types until the seat has a client package (ADR 0625 §4). For importers: a module's fingerprint is Fp, and the answer's Input is a JSON object (map[string]any), not raw bytes. compose-candidate's input is the boolean the engine and the checker read; the seat set's test takes that correction, and the whole seat's added verbs as da653ce reads them. EngineSeat leaves testdata/seat-names.txt. Claude-Session: https://claude.ai/code/session_01JG73KG7AfSdh6FP8VrBqCn --- go/catalogue/engine_compose_candidate_test.go | 4 +- go/catalogue/engine_seat.go | 21 +++-- go/catalogue/seats_test.go | 14 ++- go/delivery/compose_candidate.go | 86 +++++++++---------- go/delivery/compose_candidate_test.go | 7 +- go/testdata/seat-names.txt | 1 - 6 files changed, 67 insertions(+), 66 deletions(-) diff --git a/go/catalogue/engine_compose_candidate_test.go b/go/catalogue/engine_compose_candidate_test.go index fb34000..b2c3797 100644 --- a/go/catalogue/engine_compose_candidate_test.go +++ b/go/catalogue/engine_compose_candidate_test.go @@ -2,7 +2,7 @@ package catalogue import "testing" -// The node-engine's compose-candidate verb (ADR 0593 §7) is optional, asks the candidate, and takes input as a switch. +// The node-engine's compose-candidate verb (ADR 0593 §7) is optional, asks the candidate, and takes input as a boolean, as the engine and the checker read it. func TestTheEngineServesComposeCandidate(t *testing.T) { for _, v := range EngineVerbs() { if v.Name != "compose-candidate" { @@ -10,7 +10,7 @@ func TestTheEngineServesComposeCandidate(t *testing.T) { } props, _ := v.Input["properties"].(map[string]any) input, _ := props["input"].(map[string]any) - if !v.Optional || props["candidate"] == nil || input == nil || input["enum"] == nil { + if !v.Optional || props["candidate"] == nil || input == nil || input["type"] != "boolean" { t.Fatalf("compose-candidate reads as %+v", v) } return diff --git a/go/catalogue/engine_seat.go b/go/catalogue/engine_seat.go index bcbbd2f..34d39eb 100644 --- a/go/catalogue/engine_seat.go +++ b/go/catalogue/engine_seat.go @@ -1,23 +1,26 @@ package catalogue -import contractseats "git.novox.be/novox/mesh-contracts/go/seats" +import "git.novox.be/novox/mesh-contracts/go/seats/nodeengine" // EngineSeat is the node seat every node's engine holds. -const EngineSeat = contractseats.NodeEngine +// +// Deprecated: the seat is defined whole in mesh-contracts (nodeengine.Seat, its verbs, events, subjects and message +// types); kept for its importers until their next change takes it from there (testdata/seat-names.txt). +const EngineSeat = nodeengine.Seat // EngineVerbs are the node-engine seat's verbs, in the order ADR 0563 §1 names them: its definition's, read from the -// generated seat list (design 26). +// generated seat list (design 26), each with its description and its input's schema as discovery shows them. Their +// names are nodeengine.Verbs(). func EngineVerbs() []Verb { s, _ := seatIn(defaultSeats, EngineSeat) return append([]Verb(nil), s.Serves...) } -// EngineEvents are what the seat says of an act, each naming the act, the path or access, the caller as the runtime -// proved it and the ask (ADR 0563 §1). -var EngineEvents = func() []string { - s, _ := seatIn(defaultSeats, EngineSeat) - return append([]string(nil), s.Emits...) -}() +// EngineEvents are what the seat says: of an act, each naming the act, the path or access, the caller as the runtime +// proved it and the ask (ADR 0563 §1), and that what its engine reads of a manifest changed (ADR 0618 §4). +// +// Deprecated: the seat's contract names them, nodeengine.Events(); kept for its importers. +var EngineEvents = nodeengine.Events() // seatIn is the seat of that name in a list. func seatIn(list []Seat, name string) (Seat, bool) { diff --git a/go/catalogue/seats_test.go b/go/catalogue/seats_test.go index dd13a59..4f6bdec 100644 --- a/go/catalogue/seats_test.go +++ b/go/catalogue/seats_test.go @@ -56,9 +56,11 @@ func TestUseSeatsReplacesTheSetButNeverEmptiesIt(t *testing.T) { } whole := []string{"npm-package-registry", "mesh-broker", "mesh-config", "mesh-vault", "mesh-delivery-checker", "node-engine"} dropped := map[string]bool{"mesh-vault.accept": true, "mesh-vault.move-asks": true} - // Verbs a seat gained in the milestone, each optional until its holders serve it: node-engine's manifest-reads (ADR - // 0618 §3) came with its move whole. - addedVerbs := map[string]bool{"node-engine.pull-waits": true, "node-engine.manifest-reads": true} + // Verbs a seat at the seat list's grain gained in the milestone, each optional until its holders serve it. + addedVerbs := map[string]bool{"node-engine.pull-waits": true} + // A verb whose input the whole definition corrected: compose-candidate's input is the boolean the engine and the + // checker read, where the compiled list said a word (engine_compose_candidate_test.go reads it). + retyped := map[string]bool{"node-engine.compose-candidate": true} norm := func(v any) any { b, _ := json.Marshal(v) var out any @@ -101,7 +103,11 @@ func TestUseSeatsReplacesTheSetButNeverEmptiesIt(t *testing.T) { } continue } - kept = append(kept, byName[v.Name]) + kv := byName[v.Name] + if retyped[want.Name+"."+v.Name] { + kv.Input = v.Input + } + kept = append(kept, kv) } g.Serves, g.Emits, g.Accepts = kept, want.Emits, want.Accepts // A verb the whole seat added (new, optional while its holders catch up) is no verb of the compiled list. diff --git a/go/delivery/compose_candidate.go b/go/delivery/compose_candidate.go index 84cdefd..26f051d 100644 --- a/go/delivery/compose_candidate.go +++ b/go/delivery/compose_candidate.go @@ -5,6 +5,8 @@ import ( "encoding/json" "fmt" "time" + + "git.novox.be/novox/mesh-contracts/go/seats/nodeengine" ) // A composition that does not exist yet is asked of the node-engine (novox/hq ADR 0593 §7): the seat node-engine's verb @@ -16,7 +18,9 @@ import ( const ( // EngineComposeCandidateVerb is the verb's name on the seat node-engine (catalogue.EngineSeat). - EngineComposeCandidateVerb = "compose-candidate" + // + // Deprecated: the seat's contract names it, nodeengine.VerbComposeCandidate; kept for its importers. + EngineComposeCandidateVerb = nodeengine.VerbComposeCandidate // ComposeAnswerBound is the default of the checker's setting compose-answer-bound: how long it waits for a node's // answer (ADR 0593 §7, §9). ComposeAnswerBound = 60 * time.Second @@ -45,53 +49,41 @@ const ( ModuleHeldState = "held" ) -// ComposeCandidateAsk is the verb's input: Candidate is the key of the indexer's candidate (CompositionKey, -// `.`); the engine reads that candidate itself and lays its modules over its placement. -// Input asks the engine to answer too the composition input it composed the candidate from (its own node's scope, the -// catalogue left out as composer.Request.Input says): the checker puts it into the request a build module's composer -// runs over, for a group that changes the node-engine's composition code (ADR 0593 §7). -type ComposeCandidateAsk struct { - Candidate string `json:"candidate"` - Input bool `json:"input,omitempty"` -} +// The verb's ask and answer are the seat's contract, generated in mesh-contracts (seats/node-engine): mesh-sdk keeps +// these names for their importers and the checks below over them, until the seat has a client package (ADR 0625 §4). +type ( + // ComposeCandidateAsk is the verb's input: Candidate is the key of the indexer's candidate (CompositionKey, + // `.`); the engine reads that candidate itself and lays its modules over its placement. + // Input asks the engine to answer too the composition input it composed the candidate from (its own node's scope, the + // catalogue left out as composer.Request.Input says): the checker puts it into the request a build module's composer + // runs over, for a group that changes the node-engine's composition code (ADR 0593 §7). + // + // Deprecated: the seat's contract is nodeengine.ComposeCandidateInput; kept for its importers. + ComposeCandidateAsk = nodeengine.ComposeCandidateInput + // ComposeCandidateAnswer is the verb's answer: the node, the candidate, the verdict, every module placed on the node + // sorted by name, the node's own composition (Own, Module "node"), why the node could not compose with the candidate + // at all (Refused: Modules is then empty and Verdict refused, or unready when it cannot compose yet), how long the + // engine took in milliseconds, and, when asked (ComposeCandidateAsk.Input), the composition input it composed from. + // + // Deprecated: the seat's contract is nodeengine.ComposeCandidateAnswer; kept for its importers. + ComposeCandidateAnswer = nodeengine.ComposeCandidateAnswer + // ModuleComposed is one module's composition with the candidate: the fingerprint it was composed at (the + // candidate's FP2 for a module the candidate touches, else its newest done build as composed- holds it), its + // state, the composition's digest when composed (ComposedDigestOf), whether the candidate changes it from the one + // composed without it (the checker's "what the change reaches on this node"), and why, for a refused or held one. + // + // Deprecated: the seat's contract is nodeengine.Module; kept for its importers. + ModuleComposed = nodeengine.Module +) -// ComposeCandidateAnswer is the verb's answer: the node, the candidate, the verdict, every module placed on the node -// sorted by name, the node's own composition (Own, Module "node"), why the node could not compose with the candidate at -// all (Refused: Modules is then empty and Verdict refused, or unready when it cannot compose yet), how long the engine took in milliseconds, and, when asked -// (ComposeCandidateAsk.Input), the composition input it composed from. -type ComposeCandidateAnswer struct { - V int `json:"v"` - Node string `json:"node"` - Candidate string `json:"candidate"` - Verdict string `json:"verdict"` - Modules []ModuleComposed `json:"modules"` - Own ModuleComposed `json:"own"` - Refused string `json:"refused,omitempty"` - Took int64 `json:"took"` - Input json.RawMessage `json:"input,omitempty"` -} - -// ModuleComposed is one module's composition with the candidate: the fingerprint it was composed at (the candidate's -// FP2 for a module the candidate touches, else its newest done build as composed- holds it), its state, the -// composition's digest when composed (ComposedDigestOf), whether the candidate changes it from the one composed without -// it (the checker's "what the change reaches on this node"), and why, for a refused or held one. -type ModuleComposed struct { - Module string `json:"module"` - FP string `json:"fp,omitempty"` - State string `json:"state"` - Digest string `json:"digest,omitempty"` - Changed bool `json:"changed,omitempty"` - Words string `json:"words,omitempty"` -} - -func (a ComposeCandidateAsk) check() error { +func checkComposeAsk(a ComposeCandidateAsk) error { if _, _, err := ParseCompositionKey(a.Candidate); err != nil { return fmt.Errorf("delivery: a compose-candidate ask names no candidate: %w", err) } return nil } -func (m ModuleComposed) check(node string) error { +func checkComposedModule(m ModuleComposed, node string) error { if m.Module == "" { return fmt.Errorf("delivery: a module's composition on %s names no module", node) } @@ -111,7 +103,7 @@ func (m ModuleComposed) check(node string) error { return nil } -func (a ComposeCandidateAnswer) check() error { +func checkComposeAnswer(a ComposeCandidateAnswer) error { if err := checkName("node", a.Node); err != nil { return err } @@ -133,7 +125,7 @@ func (a ComposeCandidateAnswer) check() error { } refused := false for i, m := range append([]ModuleComposed{a.Own}, a.Modules...) { - if err := m.check(a.Node); err != nil { + if err := checkComposedModule(m, a.Node); err != nil { return err } if i > 1 && a.Modules[i-2].Module >= m.Module { @@ -164,7 +156,7 @@ func strictDecode(data []byte, v any, what string) error { // EncodeComposeCandidateAsk is a as JSON; it refuses an ask naming no candidate key. func EncodeComposeCandidateAsk(a ComposeCandidateAsk) ([]byte, error) { - if err := a.check(); err != nil { + if err := checkComposeAsk(a); err != nil { return nil, err } return json.Marshal(a) @@ -176,13 +168,13 @@ func DecodeComposeCandidateAsk(data []byte) (ComposeCandidateAsk, error) { if err := strictDecode(data, &a, "a compose-candidate ask"); err != nil { return ComposeCandidateAsk{}, err } - return a, a.check() + return a, checkComposeAsk(a) } // EncodeComposeCandidateAnswer is a as JSON at Version; it refuses an unknown state, a composed module without a digest, // a refused or held one without words, a refusal of the whole naming modules, and a verdict the modules do not give. func EncodeComposeCandidateAnswer(a ComposeCandidateAnswer) ([]byte, error) { - if err := a.check(); err != nil { + if err := checkComposeAnswer(a); err != nil { return nil, err } a.V = Version @@ -199,5 +191,5 @@ func DecodeComposeCandidateAnswer(data []byte) (ComposeCandidateAnswer, error) { if err := strictDecode(data, &a, "a compose-candidate answer"); err != nil { return ComposeCandidateAnswer{}, err } - return a, a.check() + return a, checkComposeAnswer(a) } diff --git a/go/delivery/compose_candidate_test.go b/go/delivery/compose_candidate_test.go index 63e4e1d..66afd83 100644 --- a/go/delivery/compose_candidate_test.go +++ b/go/delivery/compose_candidate_test.go @@ -2,6 +2,7 @@ package delivery import ( "encoding/json" + "reflect" "strings" "testing" @@ -32,16 +33,16 @@ func TestComposeCandidateAndComposedPlanRoundTripAndRefuse(t *testing.T) { good := ComposeCandidateAnswer{Node: "ace", Candidate: key, Verdict: ComposeCandidateRefused, Took: 812, Own: ModuleComposed{Module: "node", State: ModuleComposedState, Digest: "sha256:00"}, Modules: []ModuleComposed{ - {Module: "netcheck", FP: "src2:x", State: ModuleComposedState, Digest: "sha256:01", Changed: true}, + {Module: "netcheck", Fp: "src2:x", State: ModuleComposedState, Digest: "sha256:01", Changed: true}, {Module: "route-proxy", State: ModuleRefusedState, Words: "the setting domain has no value"}, {Module: "zz", State: ModuleHeldState, Words: "held before"}, - }, Input: json.RawMessage(`{"machines":["ace"]}`)} + }, Input: map[string]any{"machines": []any{"ace"}}} raw, err = EncodeComposeCandidateAnswer(good) if err != nil { t.Fatal(err) } back, err := DecodeComposeCandidateAnswer(raw) - if err != nil || back.Verdict != ComposeCandidateRefused || len(back.Modules) != 3 || string(back.Input) != string(good.Input) { + if err != nil || back.Verdict != ComposeCandidateRefused || len(back.Modules) != 3 || !reflect.DeepEqual(back.Input, good.Input) { t.Fatalf("answer round trip: %v, %+v", err, back) } for name, mutate := range map[string]func(a *ComposeCandidateAnswer){ diff --git a/go/testdata/seat-names.txt b/go/testdata/seat-names.txt index 3002238..ce3741b 100644 --- a/go/testdata/seat-names.txt +++ b/go/testdata/seat-names.txt @@ -7,7 +7,6 @@ catalogue/checker_seat.go: CheckerSeat catalogue/control_records_seats.go: HandActsSeat catalogue/control_records_seats.go: WantsSeat catalogue/delivery_seat.go: DeliverySeat -catalogue/engine_seat.go: EngineSeat catalogue/enrolment_seat.go: EnrolmentSeat catalogue/environment_into.go: EnvironmentSeat catalogue/environment_into.go: LoginShellSeat