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 controller names it — `mesh-controller.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-controller.conditions` says what the controller 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 }