The review of 2026-10-09: the router read the absence of an agent-root condition as a pass, and its minute hung on a break-glass file, taking the work it already held. An approval now rests on the controller's verb root-free, asked live for the router's machine and the channel's, failing closed on every other answer, and a stale word on the kinds promises no verified sender. Break-glass runs in the minute, says on every channel what root did, and ends the router's own open questions. An ask offering an approval is answered only where it could be approved; a message says who asks, what each answer does and what expiry does. The router's own asks bind the account they are about; a link is checked against one per kind as it is made; an unlinked account does not come back by a code within a day; a fresh link is unlinked from the desk; no link begins while the desk cannot show its code, and a code is marked secret on every path. Links are bounded per account in the router's state; a warrant's message id cannot be foreseen. Moves to mesh-sdk 76902998 (canonical digests).
330 lines
9.8 KiB
Go
330 lines
9.8 KiB
Go
package main
|
|
|
|
// The channels, as the router sees them (novox/hq ADR 0259): kinds of the `channel` bench, each held by a
|
|
// module of its own. The router keeps no channel's client and knows no service. What it knows of a kind
|
|
// comes from two places, never from one alone:
|
|
//
|
|
// - **the controller**, in this module's membership: which kinds exist, which module holds each, and the
|
|
// capabilities its claim declares (`channel-capabilities/1`). An answer is judged by these.
|
|
// - **the channel**, on the `intake` bench under its own kind: whether it can send now (`standing`), and
|
|
// a message it could not deliver (`failed`).
|
|
//
|
|
// A message is sent by putting it on the kind's work queue (`mesh.seat.channel.accept.<verb>.<kind>`) under
|
|
// a handle the router makes; the channel keeps which of its own messages that handle is, so an edit names
|
|
// the handle. A queued message survives the channel being away.
|
|
|
|
import (
|
|
"crypto/rand"
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"sort"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"git.novox.be/novox/mesh-sdk/go/asks"
|
|
)
|
|
|
|
// StandingFresh is how long a channel's word on whether it can send is believed: a channel says it at
|
|
// least every five minutes, so a word older than this is a channel that stopped saying.
|
|
const StandingFresh = 15 * time.Minute
|
|
|
|
// KindInfo is one kind of the channel bench as the controller issued it.
|
|
type KindInfo struct {
|
|
Kind string
|
|
Module string
|
|
Node string
|
|
Capabilities []string
|
|
}
|
|
|
|
// Standing is what a channel last said of itself.
|
|
type Standing struct {
|
|
Ready bool `json:"ready"`
|
|
Why string `json:"why,omitempty"`
|
|
EditsSilently bool `json:"edits-silently,omitempty"`
|
|
At time.Time `json:"at"`
|
|
}
|
|
|
|
// Bench is every kind the router may send to, and what each said.
|
|
type Bench struct {
|
|
Now func() time.Time
|
|
// To is the account linked as the operator on a kind, where it verifies its sender: where its messages go.
|
|
To func(kind string) string
|
|
|
|
mu sync.Mutex
|
|
kinds map[string]KindInfo
|
|
standing map[string]Standing
|
|
known bool
|
|
knownAt time.Time
|
|
}
|
|
|
|
// KindsFresh is how long the controller's word on the kinds is believed for a proof: the router reads it every
|
|
// half minute, so an older word is one it could not read again. A stale word keeps the kinds for sending, and
|
|
// promises no verified sender (the review of 2026-10-09: a capability the controller withdrew must not stay).
|
|
const KindsFresh = 2 * time.Minute
|
|
|
|
// NewBench is a bench that knows no kind yet.
|
|
func NewBench(now func() time.Time) *Bench {
|
|
return &Bench{Now: now, kinds: map[string]KindInfo{}, standing: map[string]Standing{}}
|
|
}
|
|
|
|
// SetKinds takes the kinds the controller issued.
|
|
func (b *Bench) SetKinds(kinds []KindInfo) {
|
|
b.mu.Lock()
|
|
defer b.mu.Unlock()
|
|
b.kinds = map[string]KindInfo{}
|
|
for _, k := range kinds {
|
|
b.kinds[k.Kind] = k
|
|
}
|
|
b.known = true
|
|
b.knownAt = b.Now()
|
|
}
|
|
|
|
// Stand takes what a kind said of itself.
|
|
func (b *Bench) Stand(kind string, s Standing) {
|
|
b.mu.Lock()
|
|
defer b.mu.Unlock()
|
|
b.standing[kind] = s
|
|
}
|
|
|
|
// Kind is one kind, as the controller issued it.
|
|
func (b *Bench) Kind(kind string) (KindInfo, bool) {
|
|
b.mu.Lock()
|
|
defer b.mu.Unlock()
|
|
k, ok := b.kinds[kind]
|
|
return k, ok
|
|
}
|
|
|
|
// Kinds is every kind, by name.
|
|
func (b *Bench) Kinds() []KindInfo {
|
|
b.mu.Lock()
|
|
defer b.mu.Unlock()
|
|
out := make([]KindInfo, 0, len(b.kinds))
|
|
for _, k := range b.kinds {
|
|
out = append(out, k)
|
|
}
|
|
sort.Slice(out, func(i, j int) bool { return out[i].Kind < out[j].Kind })
|
|
return out
|
|
}
|
|
|
|
// Promises says whether a kind's claim declares a capability: the controller's record, never the channel's.
|
|
func (b *Bench) Promises(kind, capability string) bool {
|
|
k, ok := b.Kind(kind)
|
|
if !ok {
|
|
return false
|
|
}
|
|
if capability == "verified-sender" {
|
|
b.mu.Lock()
|
|
at := b.knownAt
|
|
b.mu.Unlock()
|
|
if age := b.Now().Sub(at); age > KindsFresh || age < 0 {
|
|
return false
|
|
}
|
|
}
|
|
for _, c := range k.Capabilities {
|
|
if c == capability {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// Ready says whether a kind can send now, in words when it cannot.
|
|
func (b *Bench) Ready(kind string) error {
|
|
b.mu.Lock()
|
|
_, held := b.kinds[kind]
|
|
s, said := b.standing[kind]
|
|
known := b.known
|
|
b.mu.Unlock()
|
|
switch {
|
|
case !held && !known:
|
|
return errors.New("the mesh has not yet said which channels exist")
|
|
case !held:
|
|
return fmt.Errorf("no module holds the channel %s", kind)
|
|
case !said:
|
|
return fmt.Errorf("%s has not said whether it can send", kind)
|
|
case b.Now().Sub(s.At) > StandingFresh:
|
|
return fmt.Errorf("%s has said nothing since %s", kind, s.At.UTC().Format(time.RFC3339))
|
|
case !s.Ready:
|
|
if s.Why == "" {
|
|
return fmt.Errorf("%s says it cannot send", kind)
|
|
}
|
|
return errors.New(s.Why)
|
|
case b.Promises(kind, "verified-sender") && b.To != nil && b.To(kind) == "":
|
|
// A channel proving its sender says only to the account linked as the operator: none linked, it has
|
|
// nobody to say anything to (the correctness review of 2026-10-08).
|
|
return fmt.Errorf("no %s account is linked as the operator: send /start to the bot", Display(kind))
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (b *Bench) editsSilently(kind string) bool {
|
|
b.mu.Lock()
|
|
defer b.mu.Unlock()
|
|
return b.standing[kind].EditsSilently
|
|
}
|
|
|
|
// Roles: the away channel reaches the operator away from the machines; the desk is at them.
|
|
const (
|
|
RoleAway = "away"
|
|
RoleDesk = "desk"
|
|
)
|
|
|
|
// roleOf is a kind's role, from what its claim declares.
|
|
func (b *Bench) roleOf(kind string) string {
|
|
if b.Promises(kind, "reaches-away") {
|
|
return RoleAway
|
|
}
|
|
return RoleDesk
|
|
}
|
|
|
|
// Display is a kind as the operator reads it: "Telegram", "the desk".
|
|
func Display(kind string) string {
|
|
if kind == "desktop" {
|
|
return "the desk"
|
|
}
|
|
if kind == "" {
|
|
return kind
|
|
}
|
|
return strings.ToUpper(kind[:1]) + kind[1:]
|
|
}
|
|
|
|
// SeatChannel is one role's channel: the first kind of that role that can send, else the first of it.
|
|
type SeatChannel struct {
|
|
Bench *Bench
|
|
Role string
|
|
Publish func(subject string, body any, id string) error
|
|
}
|
|
|
|
func (c *SeatChannel) kind() string {
|
|
var first string
|
|
for _, k := range c.Bench.Kinds() {
|
|
if c.Bench.roleOf(k.Kind) != c.Role {
|
|
continue
|
|
}
|
|
if first == "" {
|
|
first = k.Kind
|
|
}
|
|
if c.Bench.Ready(k.Kind) == nil {
|
|
return k.Kind
|
|
}
|
|
}
|
|
return first
|
|
}
|
|
|
|
// Name is the kind it sends to now, or its role when no kind holds it.
|
|
func (c *SeatChannel) Name() string {
|
|
if k := c.kind(); k != "" {
|
|
return k
|
|
}
|
|
return c.Role
|
|
}
|
|
|
|
func (c *SeatChannel) Ready() error {
|
|
k := c.kind()
|
|
if k == "" {
|
|
if c.Role == RoleAway {
|
|
return errors.New("no channel reaching the operator away from the machines is held: a module claims a " +
|
|
"kind of the channel bench that promises reaches-away (novox/hq ADR 0259)")
|
|
}
|
|
return errors.New("no channel at the operator's machines is held")
|
|
}
|
|
return c.Bench.Ready(k)
|
|
}
|
|
|
|
func (c *SeatChannel) CanEdit() bool { return c.Bench.Promises(c.kind(), "edit") }
|
|
func (c *SeatChannel) SilentEdit() bool { return c.Bench.editsSilently(c.kind()) }
|
|
|
|
// Send puts a message on the kind's queue and answers the handle it is known by.
|
|
func (c *SeatChannel) Send(m Message) (string, error) {
|
|
handle := newHandle()
|
|
return handle, c.put(handle, m, false)
|
|
}
|
|
|
|
// Edit replaces the message a handle names.
|
|
func (c *SeatChannel) Edit(handle string, m Message) error {
|
|
if handle == "" {
|
|
return errors.New("no message on record to replace")
|
|
}
|
|
return c.put(handle, m, true)
|
|
}
|
|
|
|
func (c *SeatChannel) put(handle string, m Message, edit bool) error {
|
|
kind := c.kind()
|
|
if kind == "" {
|
|
return c.Ready()
|
|
}
|
|
return c.putOn(kind, handle, m, edit)
|
|
}
|
|
|
|
func (c *SeatChannel) putOn(kind, handle string, m Message, edit bool) error {
|
|
if c.Publish == nil {
|
|
return errors.New("this router cannot reach the bus")
|
|
}
|
|
verb := asks.Send
|
|
switch {
|
|
case edit:
|
|
verb = asks.Edit
|
|
case len(m.Actions) > 0:
|
|
verb = asks.Show
|
|
}
|
|
// Secret travels whatever the path: a link's code is kept by no channel (the review of 2026-10-09 found it
|
|
// dropped here, where the desk's own role sends).
|
|
out := asks.Message{Handle: handle, Title: m.Title, Body: m.Body, Urgent: m.Urgent, Silent: m.Quiet, Reply: m.Reply,
|
|
Secret: m.Secret}
|
|
if c.Bench.To != nil {
|
|
out.To = c.Bench.To(kind)
|
|
}
|
|
if m.To != "" {
|
|
out.To = m.To
|
|
}
|
|
for _, a := range m.Actions {
|
|
out.Buttons = append(out.Buttons, asks.Button{Label: a.Label, Ticket: a.Token})
|
|
}
|
|
return c.Publish(asks.ChannelSubject(verb, kind), out, handle+"."+fmt.Sprint(time.Now().UnixNano()))
|
|
}
|
|
|
|
func newHandle() string {
|
|
var b [9]byte
|
|
_, _ = rand.Read(b[:])
|
|
return "m" + hex.EncodeToString(b[:])
|
|
}
|
|
|
|
// Asker calls a tool through the mesh, as stdio.Ask does: how the open conditions are read.
|
|
type Asker func(key string, body any) (json.RawMessage, error)
|
|
|
|
// KindChannel is one kind as a channel: for a test, and for the status of each.
|
|
type KindChannel struct {
|
|
Bench *Bench
|
|
Kind string
|
|
Publish func(subject string, body any, id string) error
|
|
}
|
|
|
|
func (c *KindChannel) Name() string { return c.Kind }
|
|
func (c *KindChannel) Ready() error { return c.Bench.Ready(c.Kind) }
|
|
func (c *KindChannel) CanEdit() bool { return c.Bench.Promises(c.Kind, "edit") }
|
|
func (c *KindChannel) SilentEdit() bool { return c.Bench.editsSilently(c.Kind) }
|
|
func (c *KindChannel) Send(m Message) (string, error) {
|
|
s := &SeatChannel{Bench: c.Bench, Publish: c.Publish}
|
|
handle := newHandle()
|
|
return handle, s.putOn(c.Kind, handle, m, false)
|
|
}
|
|
func (c *KindChannel) Edit(handle string, m Message) error {
|
|
s := &SeatChannel{Bench: c.Bench, Publish: c.Publish}
|
|
return s.putOn(c.Kind, handle, m, true)
|
|
}
|
|
|
|
// kindChannels is every kind the controller says exists, as a channel; the two roles when it says none yet.
|
|
func (h *Holder) kindChannels() []Channel {
|
|
if h.Bench == nil || len(h.Bench.Kinds()) == 0 {
|
|
return h.channels()
|
|
}
|
|
var out []Channel
|
|
for _, k := range h.Bench.Kinds() {
|
|
out = append(out, &KindChannel{Bench: h.Bench, Kind: k.Kind, Publish: h.Publish})
|
|
}
|
|
return out
|
|
}
|