Main reverted G's half merge (#189, reverted by8afc3ebin #191) and then took the reader release's S1-S5, which copied G's code. G's form holds where they differ (process_bus.go, seats.go, state.go and their tests); the release's kept names stay so main's callers keep building: SeatViewPrefix beside SeatReadPrefix, and QueueInvoke's empty strings on not-ok (with its test TestQueueInvoke). The tree differs from G's headf30dee54only by those and the release's two tests (objects cap option, seat states and stream kinds). Builds: design 47 G (the milestone's sdk part, as atf30dee54) Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
52 lines
1.8 KiB
Go
52 lines
1.8 KiB
Go
package operatorask
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
// An Act is what an option of an ask binds (novox/hq ADR 0286 §10, §11): 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. It is mesh-sdk's (design 47 G
|
|
// lead answers A-198, A-219), so the seat server's gate in go/process digests an act as every asker does;
|
|
// operatorchannelclient re-exports it.
|
|
type Act map[string]string
|
|
|
|
// ActDigest is the digest an asker puts in an option's binds: SHA-256 over the act's canonical encoding, written
|
|
// "sha256:<hex>". 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")
|
|
}
|
|
names := make([]string, 0, len(act))
|
|
for k := range act {
|
|
if k == "" {
|
|
return "", fmt.Errorf("the act cannot be digested: a field has no name")
|
|
}
|
|
names = append(names, k)
|
|
}
|
|
sort.Strings(names)
|
|
var b strings.Builder
|
|
b.WriteString("novox.act.v1\n")
|
|
for _, k := range names {
|
|
canonical(&b, k)
|
|
canonical(&b, act[k])
|
|
}
|
|
sum := sha256.Sum256([]byte(b.String()))
|
|
return "sha256:" + hex.EncodeToString(sum[:]), nil
|
|
}
|
|
|
|
// 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.
|
|
func canonical(b *strings.Builder, v string) {
|
|
b.WriteString(strconv.Itoa(len(v)))
|
|
b.WriteByte(':')
|
|
b.WriteString(v)
|
|
b.WriteByte('\n')
|
|
}
|