Files
mesh-delivery/modules/mesh-delivery-teller/internal/ends/ends.go
T
jschoubben 3d058af14c D1 (teller): the teller names its own module from its credential's
Its conditions' keys, its asks' subjects, its reader and emitter, its ends bucket and its tools' first views take the module its credential names (the tools: the module the runtime serves them as) instead of constants of its name; a guard test finds none in its code.

Builds: design 47 G A-296, §428

Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 22:47:50 +01:00

82 lines
3.4 KiB
Go

// Package ends is the teller's recovery tool teller_end_ask and the value it writes (issue 774; the instruction "tools are
// how a module is spoken to"). An ask the teller holds open at the router keeps one of the three places the router gives
// its asker (asks.MostOpen) until it is answered, it lapses, or the gate or the assigner takes its key away. When that last
// never comes (on 2026-10-31 two merge asks of pull requests merged by another path held two places for a day, and a
// assignments ask was refused for want of room), a person ends the ask by hand. The teller's process serves no request
// subject (ADR 0437 §4), so the tool writes an end into the teller's state ends, which the teller watches, carries out
// and answers there, as the indexer answers indexer_take_in.
package ends
import (
"encoding/json"
"errors"
"fmt"
"strings"
"time"
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/clients/meshdeliveryclient"
)
const (
Tool = "teller_end_ask"
// State is the teller's state of ends asked by hand.
State = "ends"
)
// Bucket is that state's bucket, `<module>_ends` of the module the teller runs as, its credential's (design 47 G A-296).
func Bucket(module string) string { return module + "_" + State }
// Result is the teller's answer to an end: which ask it found (its router id and the gate's or the assigner's key it was
// asked for), whether this end ended it, and what it did in words.
type Result struct {
ID string `json:"id,omitempty"`
Key string `json:"key,omitempty"`
Ended bool `json:"ended"`
Says string `json:"says"`
}
// End is a value of the state ends: the ask named (a router id, as operator-channel's asks show it, or the key of the
// gate's or the assigner's asks it was asked for), why, who and when, and, once the teller carried it out, its answer.
type End struct {
V int `json:"v"`
Ask string `json:"ask"`
Why string `json:"why"`
By string `json:"by,omitempty"`
At time.Time `json:"at"`
Result *Result `json:"result,omitempty"`
Answered time.Time `json:"answered,omitzero"`
}
// Done says the end is answered.
func (e End) Done() bool { return !e.Answered.IsZero() && e.Result != nil }
// Decode reads a value of the state ends.
func Decode(data []byte) (End, error) {
var e End
if err := json.Unmarshal(data, &e); err != nil {
return e, err
}
if e.V != 1 {
return e, fmt.Errorf("an ends value of version %d, not 1", e.V)
}
if e.Ask == "" || e.Why == "" {
return e, errors.New("an ends value naming no ask or no why")
}
return e, nil
}
// New is the value teller_end_ask writes: its key and the value. The key is the router's spelling of the name given
// (meshdeliveryclient.AskID), which a bucket key takes whatever the name holds, and is the same for the same name, so a second end
// of one ask overwrites the first.
func New(ask, why, by string, at time.Time) (string, []byte, error) {
ask, why = strings.TrimSpace(ask), strings.TrimSpace(why)
if ask == "" {
return "", nil, errors.New("name the ask to end (ask): its router id, as operator-channel's asks show it, or the key of the gate's or the assigner's asks")
}
if why == "" {
return "", nil, errors.New("say why the ask is ended (why), in plain words: it is logged with who ended it")
}
data, err := json.Marshal(End{V: 1, Ask: ask, Why: why, By: by, At: at.UTC()})
return meshdeliveryclient.AskID(ask), data, err
}