G C-1 (Go): a bundle reads its tool runner's capability words once and names a word not stated

mesh/capabilities with its answer {words}, an unknown-call answer read as none,
the node from MESH_NODE; put-sessions and finding-calls named beside
mesh/publish.

Builds: design 47 G §576, §488, §622

Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
This commit is contained in:
2026-11-11 20:29:14 +01:00
parent a9b3182753
commit 311b7380aa
3 changed files with 152 additions and 0 deletions
+28
View File
@@ -0,0 +1,28 @@
package buscalls
import (
"encoding/json"
"testing"
)
// Design 47 G §587, §622: the ledger's upkeep calls have their requests and answers, and every family its word.
func TestTheLedgerUpkeepCallsAndTheirWords(t *testing.T) {
var removal LedgerRemoveRequest
if err := json.Unmarshal([]byte(`{"ledger":"records","instance":"r1"}`), &removal); err != nil || removal.Ledger != "records" || removal.Instance != "r1" {
t.Errorf("the removal read as %+v (%v)", removal, err)
}
raw, _ := json.Marshal(LedgerRemoved{Instance: "r1", Operations: 3, Bytes: 900})
if string(raw) != `{"instance":"r1","operations":3,"bytes":900}` {
t.Errorf("the removal's answer reads %s", raw)
}
raw, _ = json.Marshal(Fill{Count: 2, Bytes: 10, MaxBytes: 100})
if string(raw) != `{"count":2,"bytes":10,"max_bytes":100}` || LedgerFill != "mesh/ledger.fill" || LedgerRemove != "mesh/ledger.remove" {
t.Errorf("the fill reads %s, calls %s %s", raw, LedgerFill, LedgerRemove)
}
for word, want := range map[string]string{BusStateCalls: "bus-state-calls", LedgerCalls: "ledger-calls",
LedgerUpkeepCalls: "ledger-upkeep-calls", SentObjectsCalls: "sent-objects-calls"} {
if word != want {
t.Errorf("the word %q is %q", want, word)
}
}
}
+87
View File
@@ -0,0 +1,87 @@
package stdio
import (
"encoding/json"
"errors"
"fmt"
"os"
"slices"
"sync"
"time"
)
// The call a bundle reads its tool runner's capability words with, and the words of the families that sit beside
// `mesh/publish` and `mesh/subscribe` (design 47 G §576, §622). The words of the bus-state families are in buscalls.
const (
// CapabilitiesCall is answered with {words}: the capability words the tool runner states.
CapabilitiesCall = "mesh/capabilities"
// CapPutSessions is the put and get sessions' word (§457, §518), CapFindingCalls the acknowledged publish and the
// subscription for new messages the finding writer uses (§559). CapSeatSettleDelay (seat.go) is the third.
CapPutSessions = "put-sessions"
CapFindingCalls = "finding-calls"
)
// capabilityWords are the words the tool runner states, read once at the first call that needs one and kept for as long
// as the bundle runs: a bundle lives no longer than the tool runner that started it (design 47 G §576).
type capabilityWords struct {
mu sync.Mutex
read bool
words []string
}
var capabilities capabilityWords
// get answers the words, asking once with ask. A tool runner of before G answers `mesh/capabilities` as an unknown call,
// which reads as no word stated; any other failure is returned and asked again at the next call.
func (c *capabilityWords) get(ask func() (json.RawMessage, error)) ([]string, error) {
c.mu.Lock()
defer c.mu.Unlock()
if c.read {
return c.words, nil
}
raw, err := ask()
switch {
case errors.Is(err, errors.ErrUnsupported):
c.words = nil
case err != nil:
return nil, fmt.Errorf("reading the tool runner's capability words: %w", err)
default:
var a struct {
Words []string `json:"words"`
}
if err := json.Unmarshal(raw, &a); err != nil {
return nil, fmt.Errorf("the tool runner's capability words: %w", err)
}
c.words = a.Words
}
c.read = true
return c.words, nil
}
// require is nil when word is among the stated ones, else an error naming the word and the node (design 47 G §488, §576).
func (c *capabilityWords) require(word string, ask func() (json.RawMessage, error)) error {
words, err := c.get(ask)
if err != nil {
return err
}
if slices.Contains(words, word) {
return nil
}
node := os.Getenv("MESH_NODE")
if node == "" {
node = "unknown"
}
return fmt.Errorf("the tool runner of node %s does not state the capability word %q: a call of its family is refused until it does", node, word)
}
// RequireCapability fails at once, naming the word and the node, when the tool runner this bundle runs under does not
// state word; it reads the words once (design 47 G §488, §576).
func RequireCapability(word string) error {
return capabilities.require(word, func() (json.RawMessage, error) {
c := awaitChannel()
if c == nil {
return nil, errors.New("a capability is read through the runtime that launched the bundle, and Serve has not run")
}
return c.ask(CapabilitiesCall, map[string]any{}, 30*time.Second)
})
}
+37
View File
@@ -0,0 +1,37 @@
package stdio
import (
"encoding/json"
"errors"
"strings"
"testing"
)
// Design 47 G §576: the words are read once, an unknown-call answer reads as none, and a word not stated fails naming it
// and the node (from MESH_NODE).
func TestACapabilityWordIsReadOnceAndNamedWhenNotStated(t *testing.T) {
t.Setenv("MESH_NODE", "ace")
asked := 0
var c capabilityWords
ask := func() (json.RawMessage, error) { asked++; return json.RawMessage(`{"words":["put-sessions"]}`), nil }
if err := c.require(CapPutSessions, ask); err != nil {
t.Fatal(err)
}
err := c.require(CapFindingCalls, ask)
if err == nil || !strings.Contains(err.Error(), CapFindingCalls) || !strings.Contains(err.Error(), "ace") || asked != 1 {
t.Errorf("a word not stated: %v after %d asks", err, asked)
}
var old capabilityWords
unknown := func() (json.RawMessage, error) {
asked++
return nil, &refused{message: "no mesh/capabilities", code: CodeNoMethod}
}
if err := old.require(CapPutSessions, unknown); err == nil || !strings.Contains(err.Error(), CapPutSessions) {
t.Errorf("a tool runner of before G stated %v", err)
}
var failing capabilityWords
if err := failing.require(CapPutSessions, func() (json.RawMessage, error) { return nil, errors.New("pipe closed") }); err == nil ||
!strings.Contains(err.Error(), "pipe closed") || failing.read {
t.Errorf("a failed read: %v, kept %v", err, failing.read)
}
}