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) }) }