From 33d9b288698f1c9930db4b821e19e5f203db4208 Mon Sep 17 00:00:00 2001 From: jochen Date: Thu, 12 Nov 2026 06:09:18 +0100 Subject: [PATCH] A3d item 1: operatorchannelclient re-exports mesh-sdk's act and its digest, one form MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Builds: design 47 G §677, lead answers A-198, A-219 Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h --- .../go/clients/operatorchannelclient/ask.go | 34 +++---------------- .../clients/operatorchannelclient/ask_test.go | 6 ++++ 2 files changed, 11 insertions(+), 29 deletions(-) diff --git a/operator-and-conversation/go/clients/operatorchannelclient/ask.go b/operator-and-conversation/go/clients/operatorchannelclient/ask.go index eb5a5ed..f0a28cb 100644 --- a/operator-and-conversation/go/clients/operatorchannelclient/ask.go +++ b/operator-and-conversation/go/clients/operatorchannelclient/ask.go @@ -13,12 +13,12 @@ import ( "errors" "fmt" "regexp" - "sort" "strconv" "strings" "time" seat "git.novox.be/novox/mesh-contracts/operator-and-conversation/go/seats/operatorchannel" + "git.novox.be/novox/mesh-sdk/go/operatorask" ) // The levels an option is answered at: how much proof its answer needs (novox/hq ADR 0234 §8, the glossary's @@ -57,35 +57,11 @@ const ( OutcomeRefused = "refused" // it was never shown: Words says why ) -// An Act is what an option binds: named fields, each a string — the verb, the machine, the level, and each -// argument under a name of its own ("arg.delivery"). Flat on purpose: its digest is over these names and -// values alone, never over how a language happens to encode a struct. -type Act map[string]string +// An Act is what an option binds: mesh-sdk's operatorask.Act, re-exported (design 47 G lead answers A-198, A-219). +type Act = operatorask.Act -// ActDigest is the digest an asker puts in an option's Binds: SHA-256 over the act's canonical encoding -// (canonical), written "sha256:". The same names and values give the same digest in any language, whatever -// order they were set in; a field renamed, added or emptied gives another. -func ActDigest(act Act) (string, error) { - if len(act) == 0 { - return "", fmt.Errorf("the act cannot be digested: it names nothing") - } - keys := make([]string, 0, len(act)) - for k := range act { - if k == "" { - return "", fmt.Errorf("the act cannot be digested: a field has no name") - } - keys = append(keys, k) - } - sort.Strings(keys) - var b strings.Builder - b.WriteString("novox.act.v1\n") - for _, k := range keys { - canonical(&b, k) - canonical(&b, act[k]) - } - sum := sha256.Sum256([]byte(b.String())) - return "sha256:" + hex.EncodeToString(sum[:]), nil -} +// ActDigest is the digest an asker puts in an option's Binds: mesh-sdk's operatorask.ActDigest, re-exported. +func ActDigest(act Act) (string, error) { return operatorask.ActDigest(act) } // canonical writes one value as its length in bytes, a colon, the bytes and a newline: no value can be read // as another's end or start, so two different sequences of values never encode the same. diff --git a/operator-and-conversation/go/clients/operatorchannelclient/ask_test.go b/operator-and-conversation/go/clients/operatorchannelclient/ask_test.go index 9d32152..c025c4e 100644 --- a/operator-and-conversation/go/clients/operatorchannelclient/ask_test.go +++ b/operator-and-conversation/go/clients/operatorchannelclient/ask_test.go @@ -10,6 +10,7 @@ import ( "time" seat "git.novox.be/novox/mesh-contracts/operator-and-conversation/go/seats/operatorchannel" + "git.novox.be/novox/mesh-sdk/go/operatorask" ) var now = time.Date(2026, 10, 8, 14, 0, 0, 0, time.UTC) @@ -189,6 +190,11 @@ func TestDigestsAreCanonicalOverNamedFields(t *testing.T) { Digest(gold) != "sha256:997af29a80663c5eb7d9dd38fc029c4a15fd726220b25e3c7cdc54d5a60bbbae" { t.Errorf("the digests changed with the move: %s, %s", Digest(held()), Digest(gold)) } + // The act is mesh-sdk's, re-exported (A-198, A-219): one form, so the seat server's gate digests as an asker does. + var shared operatorask.Act = Act{"verb": "release", "arg.delivery": "hq"} + if d, _ := operatorask.ActDigest(shared); d == "" { + t.Error("mesh-sdk's digest of the act is empty") + } a1, _ := ActDigest(Act{"verb": "release", "arg.delivery": "hq"}) b := Act{} b["arg.delivery"] = "hq"