messenger: say each ask taken on the bus as operator-channel.asked.<asker>, so a session hears an approval request open
The operator, 2026-10-30: "you should be able to hook into approval requests so you also get a notification". The router said only an ask's end (decided.<asker>); it now says every ask it takes, open or held, on the seat's event asked.<asker>: its id, asker, about, headline, state and expiry, never its bindings or whole words. The seat declares asked among its events and names it by caller, as decided. The subject is named in the module until the SDK's asks contract carries it.
This commit is contained in:
@@ -161,7 +161,11 @@ func (m *memAsks) PutLinksAsked(key string, times []time.Time) error {
|
||||
var rootFreeOf = map[*Holder]map[string]string{}
|
||||
|
||||
// told is what the router published to askers, by subject.
|
||||
type told struct{ by map[string][]asks.Warrant }
|
||||
type told struct {
|
||||
by map[string][]asks.Warrant
|
||||
// asked are the asks the router said it took, by subject (sayAsked).
|
||||
asked map[string][]map[string]any
|
||||
}
|
||||
|
||||
func (w *told) of(asker string) []asks.Warrant { return w.by[asks.DecidedSubject(asker)] }
|
||||
|
||||
@@ -171,7 +175,7 @@ func newRouter(t *testing.T) (*Holder, *fakeChannel, *fakeChannel, *clock, *memA
|
||||
t.Helper()
|
||||
h, tg, dt, c, _ := newHolder(t)
|
||||
store := newMemAsks()
|
||||
w := &told{by: map[string][]asks.Warrant{}}
|
||||
w := &told{by: map[string][]asks.Warrant{}, asked: map[string][]map[string]any{}}
|
||||
bench := NewBench(c.now)
|
||||
bench.SetKinds([]KindInfo{
|
||||
{Kind: "telegram", Module: "telegram", Node: "anchor", Capabilities: []string{"deliver", "reaches-away", "choice", "edit",
|
||||
@@ -204,6 +208,9 @@ func newRouter(t *testing.T) (*Holder, *fakeChannel, *fakeChannel, *clock, *memA
|
||||
if wt, ok := body.(asks.Warrant); ok {
|
||||
w.by[subject] = append(w.by[subject], wt)
|
||||
}
|
||||
if m, ok := body.(map[string]any); ok && strings.HasPrefix(subject, "mesh.seat."+asks.Seat+".event."+AskedEvent+".") {
|
||||
w.asked[subject] = append(w.asked[subject], m)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return h, tg, dt, c, store, w
|
||||
@@ -325,6 +332,10 @@ func TestWithoutALinkedAccountOnlyTheMeshMCPServerApproves(t *testing.T) {
|
||||
!strings.Contains(rec.HeldWhy, "no Telegram account is linked") {
|
||||
t.Fatalf("not held: %+v %+v", rec, w.of("mesh-delivery"))
|
||||
}
|
||||
// Held or shown, the ask is said taken on the bus for its asker, so a session hears it open (2026-10-30).
|
||||
if said := w.asked[askedSubject("mesh-delivery")]; len(said) != 1 || said[0]["ask"] != "d-1" || said[0]["state"] != StateHeld {
|
||||
t.Fatalf("the ask taken is not said on %s: %+v", askedSubject("mesh-delivery"), said)
|
||||
}
|
||||
if len(dt.sends) != 0 {
|
||||
t.Errorf("the desk was offered an ask with an approval: %+v", dt.sends)
|
||||
}
|
||||
|
||||
@@ -560,6 +560,7 @@ func (h *Holder) TakeAsk(verb, asker string, body json.RawMessage) error {
|
||||
} else {
|
||||
h.keepAsk(key, rec, 0)
|
||||
}
|
||||
h.sayAsked(asker, a, rec)
|
||||
if rec.State == StateHeld {
|
||||
h.Logf("[messenger] %s asks %q (%s): held, not shown: %s", asker, a.Headline, a.ID, rec.HeldWhy)
|
||||
return nil
|
||||
@@ -568,6 +569,31 @@ func (h *Holder) TakeAsk(verb, asker string, body json.RawMessage) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// AskedEvent is the seat's event that an ask was taken, said for its asker as decided is (ADR 0259 §3): a session
|
||||
// waits on `operator-channel.asked.<asker>` to hear an ask open, and on `operator-channel.decided.<asker>` to hear
|
||||
// its answer (the operator, 2026-10-30: "you should be able to hook into approval requests so you also get a
|
||||
// notification"). The SDK names decided's subject; asked's is named here until the SDK's asks contract carries it.
|
||||
const AskedEvent = "asked"
|
||||
|
||||
// askedSubject is where the router says an ask of an asker was taken.
|
||||
func askedSubject(asker string) string {
|
||||
return "mesh.seat." + asks.Seat + ".event." + AskedEvent + "." + asker
|
||||
}
|
||||
|
||||
// sayAsked says on the bus that an ask was taken, open or held: its id, asker, what it is about, its headline (the
|
||||
// words the operator's phone shows, already judged to carry nothing that may not leave the mesh) and when it ends.
|
||||
// Never its options' bindings or its whole words. A failure is said in the log and holds nothing: the ask stands.
|
||||
func (h *Holder) sayAsked(asker string, a asks.Ask, rec AskRecord) {
|
||||
if h.Publish == nil {
|
||||
return
|
||||
}
|
||||
body := map[string]any{"ask": a.ID, "asker": asker, "about": a.About, "headline": a.Headline, "state": rec.State,
|
||||
"opened": rec.Opened.UTC(), "expires": a.Expires}
|
||||
if err := h.Publish(askedSubject(asker), body, "asked."+ticket()); err != nil {
|
||||
h.Logf("[messenger] the ask %s of %s is taken and could not be said on the bus: %v", a.ID, asker, err)
|
||||
}
|
||||
}
|
||||
|
||||
// ticketsFor is every option's ticket on every kind that can carry the ask's highest level now: an ask with an
|
||||
// approval is answered only where it could be approved (answerLevel). None when no ready channel can.
|
||||
func (h *Holder) ticketsFor(a asks.Ask) map[string]Ticket {
|
||||
|
||||
@@ -64,8 +64,8 @@ func TestItDeclaresTheSeatsOfAnAskAndHoldsTheOperatorChannel(t *testing.T) { //
|
||||
m, _ := readManifest(t)
|
||||
verbs := []string{"open", "history", "notify", "tell", "asks"}
|
||||
want := []seat{
|
||||
{Name: asks.Seat, Scope: "mesh", Serves: verbs, Accepts: []string{"ask", "cancel", "condition"}, Emits: []string{"decided"},
|
||||
ByCaller: []string{"ask", "cancel", "condition", "decided"}, Records: []string{"asks"}},
|
||||
{Name: asks.Seat, Scope: "mesh", Serves: verbs, Accepts: []string{"ask", "cancel", "condition"}, Emits: []string{"decided", "asked"},
|
||||
ByCaller: []string{"ask", "cancel", "condition", "decided", "asked"}, Records: []string{"asks"}},
|
||||
{Name: asks.ChannelSeat, Scope: "mesh", Kinded: true, Accepts: []string{asks.Show, asks.Edit, asks.Send}},
|
||||
{Name: asks.IntakeSeat, Scope: "mesh", Kinded: true,
|
||||
Emits: []string{asks.Chosen, asks.Link, asks.FailedWhat, asks.StandingWhat}, Proofs: []string{asks.CodeProof}},
|
||||
|
||||
@@ -20,13 +20,15 @@
|
||||
"condition"
|
||||
],
|
||||
"emits": [
|
||||
"decided"
|
||||
"decided",
|
||||
"asked"
|
||||
],
|
||||
"by-caller": [
|
||||
"ask",
|
||||
"cancel",
|
||||
"condition",
|
||||
"decided"
|
||||
"decided",
|
||||
"asked"
|
||||
],
|
||||
"records": [
|
||||
"asks"
|
||||
|
||||
Reference in New Issue
Block a user