Files
mesh-catalog/modules/messenger/cmd/messenger/preview.go
T
jschoubben a8c1c275dd messenger hears and asks the condition keeper alone, never the controller (ADR 0564 clause 9)
The three mesh-controller condition-event consumes, the invoke of mesh-controller.conditions, the follow switch
(takeover.go) and its read of mesh-conditions.takeover are gone: the messenger consumes mesh-conditions' events and
reads its open conditions from seat mesh-conditions.conditions at its start. An ask of the keeper is shown on a
condition's message like the controller's was. The manifest test now refuses any controller entry; the event test
refuses the controller's events; the words fixture's action verb names the keeper's. No test added.

(issue 774, 774/S12)

Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
2026-11-02 00:19:58 +01:00

138 lines
5.5 KiB
Go

package main
// What a condition's message would say, read before it is sent (novox/hq issue 422). The words a
// condition reaches the operator with are composed here, in this router, from the condition's own plain
// words and the line on the issues that track it (tracking.go). Until this tool, the only way to read
// them was to wait for a send and look at the operator's screen: the channels keep the text of nothing
// they sent (`Sent` holds a key and an outcome, never words), and a notification still on the desk is
// not in the notifier's history. So a wording could not be proven without nagging the operator.
//
// It is the sending path's own walk and nothing beside it: `allowedWords` (holder.go), which composes
// the message, carries the tracking line into it and reduces it where the content rule refuses its
// words — the same function `words` calls for a send, so a preview cannot drift from one. It records no
// refusal, states nothing on the bus, queues nothing, sends nothing and changes no record: the walk runs
// against a copy.
//
// What it does not say, because a channel adds it where a condition carries an open ask (`sayOn`): who
// asks, what each answer does, and the answers themselves. A preview of such a condition says that its
// message carries more, rather than letting the words read as the whole of it.
//
// Two messages are about a moment that has not come. A clearing is composed as if the condition cleared
// now, which the answer says, because a clearing composed from an open record would say "after a moment"
// for a condition open four hours. A silence is refused unless the condition is under one: a silence
// message says until when, and a condition nobody silenced has no such moment to say.
import (
"sort"
"strings"
)
// The messages a condition is said with, as `what` names them through the sending path (holder.go).
var previewable = []string{"raised", "reminder", "quiet-reminder", "escalated", "reopened", "cleared", "silenced"}
// Preview is the message for a condition this router holds, composed as it would be sent, and sent
// nowhere. `what` is which of the messages about it (previewable, default `raised`).
func (h *Holder) Preview(key, what string) (map[string]any, error) {
key = strings.TrimSpace(key)
if key == "" {
return nil, errorf("key is required: the condition's key, as the keeper names it — `mesh-conditions.conditions` says which are open")
}
what = strings.TrimSpace(what)
if what == "" {
what = "raised"
}
if !previewableWhat(what) {
return nil, errorf("what %q is not one of the messages a condition is said with: %s", what, strings.Join(previewable, ", "))
}
// Asked outside the work, as every tick that composes a message warms it: a preview on a cold
// tracking word would say the tracking could not be checked, which is true of this instant and not
// of the send it is read for.
h.warmTracking()
now := h.Now()
h.mu.Lock()
held, holding := h.open[key]
var r Record
if holding {
r = *held // the walk runs against a copy: a preview changes no record this router holds
}
var conditions []string
for k, rec := range h.open {
if rec.Origin == "condition" {
conditions = append(conditions, k)
}
}
h.mu.Unlock()
if !holding || r.Origin != "condition" {
sort.Strings(conditions)
switch {
case holding && len(conditions) == 0:
return nil, errorf("%s is not a condition but this router's own %s record, which is said as part of an ask, never on its own; it holds no condition",
key, r.Origin)
case holding:
return nil, errorf("%s is not a condition but this router's own %s record, which is said as part of an ask, never on its own; it holds the conditions %s",
key, r.Origin, strings.Join(conditions, ", "))
case len(conditions) == 0:
return nil, errorf("no condition %s is open here, and this router holds none: `mesh-conditions.conditions` says what the keeper holds open", key)
default:
return nil, errorf("no condition %s is open here; it holds %s", key, strings.Join(conditions, ", "))
}
}
if what == "silenced" && !r.silenced(now) {
return nil, errorf("%s is under no silence, and a silence message says until when it is quiet: there is no such message to read", key)
}
if what == "cleared" {
// As if it cleared now: the clearing says how long it was open, which an open record cannot say.
r.Cleared = now
}
asking := h.openAsk(&r) != nil // askOf takes the same lock, so this is asked outside it
m, line, refusal, ownWords := h.allowedWords(&r, what, now)
if r.Answered != "" {
m = h.answeredWords(&r, m, "")
}
out := map[string]any{
"key": key,
"what": what,
"title": m.Title,
"body": m.Body,
"sent": "nothing: this composes the words and sends nowhere",
}
if m.Urgent {
out["urgent"] = true
}
if m.Quiet {
out["quiet"] = true
}
if line != "" {
out["tracking"] = line
}
if what == "cleared" {
out["as-if"] = "it cleared now: how long it was open is said from this moment"
}
if asking {
out["carries-more"] = "this condition has an open ask, so a channel's message also carries who asks, what each answer does, and the answers themselves"
}
out["allowed"] = ownWords
if !ownWords {
// What stands above is what would leave; its own words would not, and this says why.
out["carried"] = refusal.What
if refusal.Class != "" {
out["class"] = refusal.Class
}
out["instead"] = "its own words may not leave the mesh, so this is the message as reduced for sending"
}
return out, nil
}
func previewableWhat(what string) bool {
for _, w := range previewable {
if w == what {
return true
}
}
return false
}