Files
mesh-delivery/modules/mesh-delivery-teller/internal/ends/ends.go
T
jschoubben 71a35a6376 MV1: move contracts and sdk under modules/, importers follow
The delivery repository's contracts and SDK live under modules/ as
mesh-delivery-contracts and mesh-delivery-sdk, their Go module paths
renamed to match the folders (the TypeScript names already did), and
every importer in mesh-delivery, mesh-catalog, mesh-host, mesh-tools
and mesh-sdk follows in the same group: a design as it should be, one
path per module, checked by the group's check building every importer.

Builds: design 47 G §427, §88

Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 05:09:56 +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 (
// module is the teller's own name (its manifest's module), the holder of the bucket.
module = "mesh-delivery-teller"
Tool = "teller_end_ask"
// State is the teller's state of ends asked by hand: the bucket mesh-delivery-teller_ends.
State = "ends"
Bucket = 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
}