Fix 9.7b: one reading of who holds a seat, beside the seat list; operatorask takes it
Builds: design 47 G A-126 Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
// Package operatorask is a module's asking of a person through the seat that routes asks to the operator (design 47 G
|
||||
// A-101, §700, A-74): the seat's holder is found on the seat list, an ask is published under the module's own name, the
|
||||
// A-101, §700, A-74): the seat's holder is read by seatlist.HolderOf, an ask is published under the module's own name, the
|
||||
// router's record of it is read for the warrant, and the router's `decided` is heard. It is shared code, so no module
|
||||
// keeps its own copy; it names no seat, verb or module: the generated contracts' names and types are its parameters.
|
||||
package operatorask
|
||||
@@ -10,45 +10,28 @@ import (
|
||||
"fmt"
|
||||
|
||||
stdio "git.novox.be/novox/mesh-sdk/go"
|
||||
"git.novox.be/novox/mesh-sdk/go/seatlist"
|
||||
)
|
||||
|
||||
// Caller calls a seat verb or module tool as `seat:<seat>.<verb>` or `<module>.<tool>` and returns its answer
|
||||
// (stdio.Ask is one; so is a process's calls).
|
||||
type Caller func(key string, body any) (json.RawMessage, error)
|
||||
type Caller = seatlist.HolderCaller
|
||||
|
||||
// Contract is what the asker takes from the generated contracts: the verb that answers the seat list with its seats
|
||||
// (`Lists` is its call key, `ListBody` its input) and the asking seat and its state of the router's records.
|
||||
// Contract is what the asker takes from the generated contracts: the asking seat and its state of the router's records.
|
||||
type Contract struct {
|
||||
Lists string
|
||||
ListBody any
|
||||
Seat string
|
||||
Seat string
|
||||
// StateAsks is the seat's state holding the router's record of each ask.
|
||||
StateAsks string
|
||||
}
|
||||
|
||||
// Bucket is the bucket of the router's records: the seat's state in the bucket of the seat's holder on the seat list.
|
||||
// Bucket is the bucket of the router's records: the seat's state in the bucket of the seat's holder, as seatlist reads it
|
||||
// (design 47 G A-126).
|
||||
func Bucket(call Caller, c Contract) (string, error) {
|
||||
raw, err := call(c.Lists, c.ListBody)
|
||||
module, err := seatlist.HolderOf(call, c.Seat)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("who holds the seat %s is not asked of %s: %w", c.Seat, c.Lists, err)
|
||||
return "", fmt.Errorf("the router's record of an ask is not read: %w", err)
|
||||
}
|
||||
var held struct {
|
||||
Seats []struct {
|
||||
Seat string `json:"seat"`
|
||||
Holders []struct {
|
||||
Module string `json:"module"`
|
||||
} `json:"holders"`
|
||||
} `json:"seats"`
|
||||
}
|
||||
if err := json.Unmarshal(raw, &held); err != nil {
|
||||
return "", fmt.Errorf("the answer of %s is not the seat list's: %w", c.Lists, err)
|
||||
}
|
||||
for _, s := range held.Seats {
|
||||
if s.Seat == c.Seat && len(s.Holders) > 0 {
|
||||
return s.Holders[0].Module + "_" + c.StateAsks, nil
|
||||
}
|
||||
}
|
||||
return "", fmt.Errorf("no module holds the seat %s on the seat list, so the router's record of an ask is not read", c.Seat)
|
||||
return module + "_" + c.StateAsks, nil
|
||||
}
|
||||
|
||||
// Asker asks as one module. W is the generated contract's warrant type.
|
||||
|
||||
@@ -24,7 +24,7 @@ func TestTheAskerFindsTheHolderPublishesReadsTheWarrantAndHearsDecided(t *testin
|
||||
var published any
|
||||
var heard func(stdio.Envelope) error
|
||||
a := &Asker[warrant]{
|
||||
Module: "me", Contract: Contract{Lists: "seat:lister.list", ListBody: map[string]bool{"seats": true}, Seat: "router-seat", StateAsks: "asks"},
|
||||
Module: "me", Contract: Contract{Seat: "router-seat", StateAsks: "asks"},
|
||||
Call: call,
|
||||
Record: func(b, i string) (*stdio.StateEntry, error) {
|
||||
bucket, id = b, i
|
||||
@@ -39,7 +39,7 @@ func TestTheAskerFindsTheHolderPublishesReadsTheWarrantAndHearsDecided(t *testin
|
||||
t.Errorf("Ask published %v on %q: %v", published, subject, err)
|
||||
}
|
||||
state, w, err := a.Where("a1")
|
||||
if err != nil || state != "chosen" || w == nil || w.Choice != "go" || bucket != "holder_asks" || id != "a1" || len(asked) != 1 || asked[0] != "seat:lister.list" {
|
||||
if err != nil || state != "chosen" || w == nil || w.Choice != "go" || bucket != "holder_asks" || id != "a1" || len(asked) != 1 || asked[0] != "seat:mesh-module-assigner.assignments" {
|
||||
t.Errorf("Where = %q %+v %v, read %q %q after %v", state, w, err, bucket, id, asked)
|
||||
}
|
||||
var got []warrant
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
// Who holds a seat now (design 47 G A-126, beside the seat list): the module on record as the holder of a seat, asked of
|
||||
// the seat that places modules (`mesh-module-assigner`) through its verb `assignments` with `seats` true, which answers the
|
||||
// seat's holders. It is the one such reading: a module that reads a seat holder's state (a `reads` of `seat:<seat>.<state>`,
|
||||
// A-116) and the asker of the operator (operatorask) call it, and no part keeps its own copy. It names the one seat and
|
||||
// verb it asks, as this package names the seat that serves the seat list, and no module.
|
||||
|
||||
package seatlist
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
const (
|
||||
// HolderSeat is the seat whose verb answers who holds each seat.
|
||||
HolderSeat = "mesh-module-assigner"
|
||||
// HolderVerb is its verb, asked with `seats` true.
|
||||
HolderVerb = "assignments"
|
||||
)
|
||||
|
||||
// HolderCaller calls a seat verb or module tool as `seat:<seat>.<verb>` or `<module>.<tool>` and returns its answer (stdio.Ask is
|
||||
// one; so is a process's calls).
|
||||
type HolderCaller func(key string, body any) (json.RawMessage, error)
|
||||
|
||||
// HolderKey is the address asked, as stdio.Ask takes it; a host's manifest `invokes` lists it.
|
||||
func HolderKey() string { return "seat:" + HolderSeat + "." + HolderVerb }
|
||||
|
||||
// HolderOf is the module holding the seat now: the first holder the seat's answer lists. It is an error while the answer cannot be
|
||||
// read or no module holds the seat.
|
||||
func HolderOf(call HolderCaller, seat string) (string, error) {
|
||||
raw, err := call(HolderKey(), map[string]string{"seats": "true"})
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("who holds the seat %s is not asked of %s: %w", seat, HolderKey(), err)
|
||||
}
|
||||
var held struct {
|
||||
Seats []struct {
|
||||
Seat string `json:"seat"`
|
||||
Holders []struct {
|
||||
Module string `json:"module"`
|
||||
} `json:"holders"`
|
||||
} `json:"seats"`
|
||||
}
|
||||
if err := json.Unmarshal(raw, &held); err != nil {
|
||||
return "", fmt.Errorf("the answer of %s is not the seat holders': %w", HolderKey(), err)
|
||||
}
|
||||
for _, s := range held.Seats {
|
||||
if s.Seat == seat && len(s.Holders) > 0 {
|
||||
return s.Holders[0].Module, nil
|
||||
}
|
||||
}
|
||||
return "", fmt.Errorf("no module holds the seat %s on record", seat)
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package seatlist
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestHolderOf(t *testing.T) {
|
||||
answer := `{"seats":[{"seat":"other","holders":[{"module":"x"}]},{"seat":"mesh-provisioner","holders":[{"module":"prov"},{"module":"second"}]}]}`
|
||||
for _, row := range []struct {
|
||||
name, seat, want, err string
|
||||
call HolderCaller
|
||||
}{
|
||||
{"the first holder on record", "mesh-provisioner", "prov", "", func(k string, b any) (json.RawMessage, error) {
|
||||
if k != "seat:mesh-module-assigner.assignments" || b.(map[string]string)["seats"] != "true" {
|
||||
t.Errorf("asked %q with %v", k, b)
|
||||
}
|
||||
return json.RawMessage(answer), nil
|
||||
}},
|
||||
{"no holder", "absent", "", "no module holds the seat absent", func(string, any) (json.RawMessage, error) { return json.RawMessage(answer), nil }},
|
||||
{"the ask fails", "x", "", "who holds the seat x is not asked", func(string, any) (json.RawMessage, error) { return nil, errors.New("down") }},
|
||||
{"not the holders' answer", "x", "", "is not the seat holders'", func(string, any) (json.RawMessage, error) { return json.RawMessage(`nope`), nil }},
|
||||
} {
|
||||
t.Run(row.name, func(t *testing.T) {
|
||||
got, err := HolderOf(row.call, row.seat)
|
||||
if row.err == "" && (err != nil || got != row.want) || row.err != "" && (err == nil || !strings.Contains(err.Error(), row.err)) {
|
||||
t.Errorf("HolderOf = %q, %v; want %q, %q", got, err, row.want, row.err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Vendored
+1
@@ -25,4 +25,5 @@ catalogue/seat_dependencies.go: node-service-manager
|
||||
catalogue/shares.go: node-mounts
|
||||
catalogue/shares.go: node-nfs-server
|
||||
internal/beside/beside.go: mesh-catalog
|
||||
seatlist/holder.go: mesh-module-assigner
|
||||
seatlist/seatlist.go: mesh-catalog
|
||||
|
||||
Reference in New Issue
Block a user