E-5 marked waits: a wait on a marked event runs only on the operator's warrant for that one wait

mesh_wait goes through the runner's approval gate (node-tools/internal/approval, Gate.Waited): a wait whose
subjects overlap a marked subject of any module the catalog half lists (the newest entry of every followed
branch, with the served entry of each module this runner serves) is an act of the runner's own module and
build (MESH_FINGERPRINT at its start), asked once on operator-channel as "Wait on <names> on <node>?", and
held, subscribing nothing but its ask's decided, until its timeout, its caller's going or the choice. On a
warrant it subscribes, its timeout running from then, and answers {answer, approval}. Before the marks are
read nothing is asked; a wait from a seat is never marked. The state fixture moves to meshtest so the
console's table can hold a gate.

Builds: design 34 every-event §34, §35, §36, §37; design 47 G lead answer A-168

Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
This commit is contained in:
2026-11-12 05:41:53 +01:00
parent 4116809162
commit 809288da69
12 changed files with 783 additions and 148 deletions
+38 -3
View File
@@ -20,6 +20,8 @@ import (
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
"git.novox.be/novox/mesh-sdk/go/buscompose"
"git.novox.be/novox/mesh-sdk/go/catalogue"
"github.com/novox/mesh-tools/node-tools/internal/approval"
)
// WaitDefault is how long a wait lasts when no timeout is given, and WaitLongest the most it may last
@@ -107,9 +109,42 @@ func (s *Surface) waitAnswer(ctx context.Context, args map[string]any) map[strin
}
}
listen := func(within time.Duration) (any, error) {
return s.hear(ctx, names, subjects, undeclared, known.source, unchecked, filter, within)
}
// A wait whose subjects overlap a marked event's runs only on the operator's warrant for that one wait (design 34
// every-event §34 to §36), judged by the runner's approval gate.
var answer any
if g, ok := s.Approvals.(waitGate); ok {
given := map[string]any{}
for _, k := range []string{"subjects", "from", "filter", "timeout"} {
if v, has := args[k]; has {
given[k] = v
}
}
raw, _ := json.Marshal(given)
answer, err = g.Waited(ctx, approval.Call{Address: verbWait, Name: verbWait, Caller: s.who, Args: raw},
approval.Wait{Names: names, From: from, Subjects: subjects, Filter: filter, Timeout: timeout}, listen)
} else {
answer, err = listen(timeout)
}
if err != nil {
return failure(err.Error())
}
return answerText(answer)
}
// waitGate is the runner's approval gate as mesh_wait goes through it (design 34 every-event §34 to §36).
type waitGate interface {
Waited(ctx context.Context, c approval.Call, w approval.Wait, hear func(within time.Duration) (any, error)) (any, error)
}
// hear subscribes the wait's subjects for within and answers the first matching event, or that it timed out.
func (s *Surface) hear(ctx context.Context, names, subjects, undeclared []string, source, unchecked string, filter map[string]any,
timeout time.Duration) (map[string]any, error) {
heard, ok, refused, err := s.conn.HearFirst(ctx, subjects, timeout, func(body json.RawMessage) bool { return bodyMatches(body, filter) })
if err != nil {
return failure("mesh_wait could not wait: " + err.Error())
return nil, fmt.Errorf("mesh_wait could not wait: %w", err)
}
var answer map[string]any
if !ok {
@@ -117,7 +152,7 @@ func (s *Surface) waitAnswer(ctx context.Context, args map[string]any) map[strin
"read": "nothing matching was heard; read the state once from the verb that owns it, then wait again"}
if len(undeclared) > 0 {
answer["undeclared"] = fmt.Sprintf("no declaration names %s: no seat definition and no module's entry in %s "+
"declares that event", strings.Join(undeclared, ", "), known.source)
"declares that event", strings.Join(undeclared, ", "), source)
}
} else {
name, from, _ := buscompose.EventName(heard.Subject)
@@ -139,7 +174,7 @@ func (s *Surface) waitAnswer(ctx context.Context, args map[string]any) map[strin
if len(refused) > 0 {
answer["refused"] = refused
}
return answerText(answer)
return answer, nil
}
// halfReader is a Source that also holds the catalog half of each followed branch (firstview.Views.Halves): mesh_wait