package broker 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, `.`: 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 } // WaitIssued is one family as a membership issues it: the name a session asks for, and its subject. type WaitIssued struct { Event string `json:"event"` Subject string `json:"subject"` } // 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 }