Files
mesh-catalog/modules/messenger/cmd/messenger/preview.go
T
jschoubben 15546f9d83
mesh/merge-gate pass: builds messenger → novox; no bus step; every machine composes with the change as it did without (4 of 4 compose)
mesh/repo-check pass: its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-check-typescript.sh (typescript)
mesh/delivery delivered
messenger: count the store's writes, and name an ask's record when it is the only one held (review of issue 422)
The review's two nits. A write of bytes identical to what is stored was
invisible to a comparison of content, so the test store counts its
writes and the preview's tests read that count: a persist from inside
Preview now fails five of them instead of one. And a router holding an
ask's record and no condition said it held none, rather than saying what
that key is; the branches are the other way round, with a test that
holds nothing else.
2026-10-11 00:49:38 +02: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 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
}