Files
mesh-sdk/go/buscompose/waits.go
T
jschoubben b5f8604d03 Copy the controller's bus composer into mesh-sdk as buscompose, held byte for byte to the controller's lists (issue 1377, ADR 0547 §5)
The grants part composes the bus's user list with the composer the controller uses (ADR 0547 §5), copied rather than
moved: the controller is not changed and keeps its own until it loses the lease. Left out: the bus client, the broker's
address from the environment and the client's two methods; the lease's two subjects are copied in.

One table test holds the copy to four meshes' lists and the server golden, written by the controller's own
internal/broker from the same fixtures (testdata/controller-user-lists.json).
2026-10-25 21:54:58 +01:00

95 lines
3.7 KiB
Go

package buscompose
import (
"fmt"
"strings"
)
// What a session may wait on through the mesh MCP server (novox/hq ADR 0367).
//
// **A grant apart from `consumes`, and the tool runner's alone.** The tool runner's module names in its
// manifest the event families a session may wait on, one by one; the runtime's account is granted a plain
// subscription on those subjects and nothing wider — no durable consumer, no queue of its own — and its
// membership names the same list, from which `mesh_wait` refuses any other. Both are read from this one
// composition, so the grant and what the tool runner offers cannot disagree, and nothing but the manifest
// adds a family (§6).
// Wait is one event family the tool runner's module declares: its name as the manifest gives it, and the
// emitter — a seat or a module — and the event, as the inventory resolved them.
type Wait struct {
// Name is the family as the manifest names it, `<emitter>.<event>`: what a session asks for.
Name string
Emitter string
// Seat says the emitter is a seat, whose events live under the seat's namespace (novox/hq ADR 0121).
Seat bool
// Event is the event, with any further token the subject carries: `decided.claude-code`, the asker of a
// seat that names it last (ADR 0259 §3).
Event string
// Last names what the seat's event subject ends in — "kind" for a kinded bench, "asker" for a seat naming
// its caller (ADR 0259 §3) — and is empty for an event that ends at its name. A family needing it is waited
// on with it given, and refused without.
Last string
}
// WaitSubject is where a family's events land. Each token is checked before it becomes a subject: a
// wildcard, a dot inside a name or an empty token would widen the grant.
func WaitSubject(w Wait) (string, error) {
if !safeSubject.MatchString(w.Emitter) {
return "", fmt.Errorf("%q cannot name an emitter of a waited event: it would widen the subject", w.Emitter)
}
for _, t := range strings.Split(w.Event, ".") {
if !safeSubject.MatchString(t) {
return "", fmt.Errorf("%q cannot name a waited event: each of its names is plain, never a wildcard "+
"(novox/hq ADR 0367 §2)", w.Event)
}
}
if w.Last != "" && !strings.Contains(w.Event, ".") {
return "", fmt.Errorf("%s names no %s: %s's events are said for each %s, so a family is %s.%s.<%s>; "+
"without it no event would ever be heard (novox/hq ADR 0367 §5)",
w.Name, w.Last, w.Emitter, w.Last, w.Emitter, w.Event, w.Last)
}
if w.Seat {
return "mesh.seat." + w.Emitter + ".event." + w.Event, nil
}
return "mesh.mod." + w.Emitter + ".event." + w.Event, nil
}
// waitGrants is the subscription the tool runner's account is granted for its module's families, and an
// error for a carried module other than the tool runner's that declares any (ADR 0367 §2).
func waitGrants(carries []Declared) ([]string, error) {
var sub []string
for _, d := range carries {
if len(d.Waits) == 0 {
continue
}
if d.Module != RuntimeModule {
return nil, fmt.Errorf("%s declares what a session waits on; only %s, the tool runner's module, "+
"does (novox/hq ADR 0367 §2)", d.Module, RuntimeModule)
}
for _, w := range d.Waits {
s, err := WaitSubject(w)
if err != nil {
return nil, err
}
sub = append(sub, s)
}
}
return sub, nil
}
// waitsIssuedFor is the families a membership names: the tool runner's module's, and nobody else's.
func waitsIssuedFor(d Declared) []WaitIssued {
if d.Module != RuntimeModule {
return nil
}
var out []WaitIssued
for _, w := range d.Waits {
s, err := WaitSubject(w)
if err != nil {
continue // refused where the account is composed, which names it
}
out = append(out, WaitIssued{Event: w.Name, Subject: s})
}
return out
}