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
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.
138 lines
5.5 KiB
Go
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
|
|
}
|