ADR 0253: say whether the operator is needed, keep quiet what needs nothing, answer from the notification
mesh/merge-gate pass: the change touches no module of the mesh's graph
mesh/repo-check pass: its merge-check.sh passed
mesh/delivery delivered
mesh/delivery-group group feat/plain-notifications delivered: every member is delivered

The operator read the reworded popup and could not tell whether to act, and was
sent to open a session to act at all. The record now decides the verdict, the
quiet rule and the path by which a notification is answered.
This commit is contained in:
jochen
2026-10-08 14:00:38 +02:00
parent fc3c42318f
commit 356c5f23f4
5 changed files with 227 additions and 127 deletions
+5 -4
View File
@@ -421,10 +421,11 @@ asking, never the act. **Decided by** ADR 0152, 0175, 0234.
holder of kind `desktop`) and not *bot* (that is one service's account).
- **router** — the module that holds the `operator-channel` seat, orders the channels and checks every
sender ([ADR 0234](../02-DECISIONS/0234-the-mesh-holds-a-conversation-with-its-operator.md)).
- **headline / explanation / resolved line** — what a condition says to the operator in plain words: a
few words naming the thing and what is wrong; one or two sentences on what happened, what it means and
whether to act; the line said when it clears. Carried by the condition beside its **summary**, which
keeps the plans, commits and verbs for whoever looks closer
- **headline / verdict / explanation / resolved line** — what a condition says to the operator in plain
words: a few words naming the thing and what is wrong; "Nothing for you to do." or "Needs you:" and the
one thing the operator can do themselves (its **needs**); one or two sentences on what happened and what
it means; the line said when it clears. Its **actions** are the answers a notification offers. Carried by
the condition beside its **summary**, which keeps the plans, commits and verbs for whoever looks closer
([ADR 0253](../02-DECISIONS/0253-a-condition-says-itself-to-the-operator-in-plain-words-beside-its-summary.md)).
- **ask** — a request for the operator's input, of a declared kind (yes-no, one-of, text, number,
date, acknowledge). An **authorising ask** is one whose answer performs an action; the controller
@@ -4,10 +4,10 @@ status: accepted
date: 2026-10-08
deciders: jochen
reconstructed: false
extends: 02-DECISIONS/0227-the-core-holds-nine-rules-each-checked-and-is-built-to-them-in-six-phases.md
extends: 02-DECISIONS/0234-the-mesh-holds-a-conversation-with-its-operator.md
---
# 253. A condition says itself to the operator in plain words, beside its summary
# 253. A condition says itself to the operator in plain words beside its summary, says whether the operator is needed, and is answered from the notification
## Context
@@ -18,6 +18,19 @@ plan id and two verbs' syntax in backticks; then `about:`, `since:` in UTC, `key
reads a notification between other work, and has to understand it in one pass: what happened, whether
they need to act, and what to do.
A first rewording made the same popup plain: "openrazer delivery waiting to start", then "The change to
openrazer is merged and built, and has waited 56 minutes for mesh-delivery … Nothing to do yet; it becomes
urgent after 4 hours. To start it now, have an agent start it by hand." The operator read it and asked:
*what does that notification mean, I need to do something? I need to open a session and tell it to
deliver it?* Plain words were not enough. Three more things were wrong:
- **It did not say plainly whether the operator was needed.** "Nothing to do yet" and "to start it now"
in one sentence read as an instruction.
- **It was shown at all.** A walk waiting under its bound needs nothing from anyone; the mesh says it
again when it does.
- **What it offered could not be done from where it was read.** "Have an agent start it by hand" sends the
operator to open a session, find the verb and say why. That is the work a notification should save.
What the mesh had, on the main branches of 2026-10-08:
- **A condition had one line of words.** To-be 45 §2 gave it a `summary`, "one line in the mesh's words".
@@ -25,124 +38,182 @@ What the mesh had, on the main branches of 2026-10-08:
plan, the commit and the verb that acts. That reader needs those, and keeps needing them.
- **Issue 277 holds every summary to the operator channel's content rule** (ADR 0234 §6): no address,
domain, path or secret. Identifiers, verbs and markup pass that rule, so they reached the operator.
- **The message added detail of its own.** The messenger built every message from the summary, and added
the subject, the kind, since when in UTC, the key and the verb that shows more (to-be 45 §5). A digest
added each key in brackets. A clearance was "CLEARED after …" and the whole summary again.
- **The controller raises some sixty condition kinds**, from some eighty places in its code. Each knows
what it is about. Several know more than the condition's subject says: a walk knows its modules, a
failed unit its name, a hand-act its cause.
- **Channels are about to multiply** (ADR 0234: Telegram and the desk today; other kinds later). Each
channel shows a title and a body. None of them can work out what a key means.
- **The message added detail of its own**, and a clearance repeated the whole summary.
- **The controller raises some sixty condition kinds**, from some eighty places in its code. Several know
more than the condition's subject says: a walk knows its modules, a failed unit its name, a hand-act its
cause.
- **The verbs that answer the common questions exist**, as seat verbs any granted principal may call:
`mesh-delivery.release` and `stop` (with why), the controller's `plans` with `go` and `stop` (with why),
`conditions` with `silence` (with why), and every machine's `node-service-manager.restart`.
- **The desk could not take an answer.** `node-notifier.send` had no actions. To-be 46 §11 designs them
(phase 2 of its build): the dunst holder offers them and emits the chosen one. Its router, asks and the
controller's `authorise request` and `answer` (phases 3 to 5) are not built. No dashboard exists where an
operator could release a delivery. The one way to act was an agent session.
- **ADR 0234 §8 says a desk click alone never authorises.** It defines three tiers, and *acknowledge*
"performs only what any granted principal may already do … it is not an authorisation, and needs no
proof". No verb declares a tier yet: that is phase 5.
## Considered Options
1. **A formatter in each channel**, which turns the summary into something readable. It is the smallest
change, in one module. But it has to guess what a summary means from its words. It would turn
"the walk of `<repository>` `<commit>` has waited …" into a title only by knowing every producer's
sentence, and each new producer would break it without notice. Every new channel would need it again.
The producer knows what it means; the channel does not. Rejected as the main mechanism; kept as the
fallback for a condition that carries no plain words (option 4, rule 6).
2. **Rewrite every summary in plain words.** Then the reader who looks closer loses the plan, the commit
and the verb, which are what an agent acts on. One line cannot serve both readers. Rejected.
3. **A table of words per condition kind in the messenger.** The messenger would know the controller's
kinds, which inverts ADR 0234's rule that the controller says what is wrong and the router only
routes. It also cannot see what only the producer knows, such as a walk's modules. Rejected.
4. **The condition carries plain words beside its summary, made where the condition is made.** Each
producer can say what it knows. A wording per kind covers the rest. A checked rule keeps identifiers
out. Every channel shows the same words. Chosen.
### Where the words are made
1. **A formatter in each channel**, which turns the summary into something readable. It has to guess what
a summary means from its words, breaks silently with each new producer, and is needed again for each new
channel. Rejected as the mechanism; kept as the fallback for a condition that carries no plain words.
2. **Rewrite every summary in plain words.** The reader who looks closer loses the plan, the commit and the
verb. One line cannot serve both readers. Rejected.
3. **A table of words per condition kind in the messenger.** The router would know the controller's
kinds, which inverts ADR 0234, and could not see what only the producer knows. Rejected.
4. **The condition carries plain words beside its summary, made where the condition is made.** Chosen.
### Whether the operator is needed
1. **Leave it to each explanation's wording.** The first rewording did, and the operator could not tell.
Rejected.
2. **A verdict every explanation opens with**, from a field the condition carries (`needs`): empty is
"Nothing for you to do.", otherwise "Needs you:" and that one thing. Chosen: it is the first thing read,
it is the same on every channel, and a router can act on the field.
### What is shown
1. **Everything, gently** (the desktop's fifteen-minute rhythm for warnings). The rhythm still shows a
popup that asks nothing. Rejected.
2. **Nothing that needs nothing, while it is a warning.** It stays open in the controller's conditions and
in the channel's history, and is said when it needs the operator: at its urgent bound, or when its words
change to "Needs you:". Chosen.
### How the operator answers
1. **Name the verb in the words**, for the operator to call. It sends them to a session. Rejected.
2. **Wait for the conversation design** (to-be 46 phases 2 to 6) and say "Needs you:" with nothing to press
until then. Honest, but leaves the common answers, which already exist as verbs, a session away for
weeks. Rejected for the answers that exist.
3. **The condition names its answers as actions; the desk offers them; the operator channel's holder
calls the verb.** It builds the part of to-be 46 phase 2 these answers need (the desk's actions, and the
chosen one said as an event), and nothing of the router or of authorising. Chosen.
## Decision
1. **Every condition carries three fields in plain words, beside its summary.**
1. **Every condition carries, beside its summary, words for the operator:**
- **headline**: a few words naming the thing and what is wrong, at most sixty characters, such as
"openrazer not working on `<machine>`". It is the title of every message about the condition.
- **explanation**: one or two sentences saying what happened, what it means for the operator, and
whether they need to act, at most 360 characters.
- **resolved line**: the one line said when it clears, such as "openrazer works again on `<machine>`".
- **needs**: one sentence, the one thing the operator can do themselves — "release it, or stop it.",
"log out of every session on `<machine>` and log in again." — or empty when nothing is needed.
- **explanation**: opened by its **verdict**, "Nothing for you to do." when `needs` is empty and
"Needs you:" and `needs` otherwise, then one or two sentences on what happened and what it means.
- **resolved line**: the one line said when it clears.
- **actions**: the answers the operator may give from the notification, each a label and the seat verb
with its arguments (Release, Stop, Start, Restart, Silence for a week). An argument `why` left empty is
the operator channel's to fill.
The **summary** keeps its role and its words: one line for whoever looks closer, naming plans, commits
and verbs. The events and the `conditions` verb carry all four.
The **summary** keeps its role and its words. The events and the `conditions` verb carry all of it.
2. **They are made where the condition is made.** A producer that knows more than the subject says them
itself: a walk names its modules, a failed unit its name without its suffix, a hand-act its cause and
count, a held delivery its repository and how long. Otherwise the **wording registered for the kind**
says them from the condition's subject. A kind with no wording is said from its scope ("a walk needs a
look") and reported. **A kind without words is a defect of its producer**, as a summary that carries an
itself; otherwise the wording registered for the kind does; otherwise the scope's words do, and the
kind is reported. **A kind without words is a defect of its producer**, as a summary that carries an
address is (issue 277).
3. **The plain rule.** The three fields name no identifier, no code and no time of their own: no hash, no
numbered id, no dotted name (a key, a verb, a unit's file name), no duration in code (`56m0s`), no clock
time, date or zone, no command flag, no markup and no line break. They also pass the content rule of
ADR 0234 §6. A machine's or a module's name is a name, and may appear. The controller holds every
condition to the rule when it takes an observation. Words that break the rule are replaced by the
scope's words and reported. A condition is never refused for its words.
4. **Whether to act is said, not implied.** An explanation says one of: nothing to do (and when that
changes), what the operator does, or that an agent can do it. A condition a healer gave up on
(`resolver: operator` with attempts, to-be 45 §2) has "The mesh tried to repair it and could not: it
needs you now." added to its explanation, whatever the kind's words said before.
5. **The channel shows the words and says when.** The title is the headline. "Urgent:", "Still open:" or
"Now urgent:" goes before it where that applies. The body is the explanation, then since when, in the
operator's time zone: a clock time today, a weekday this week, a date before that. A clearance is one
line: "Resolved: " and the resolved line, then how long it was open. A digest lists titles. No message
carries a key, an id, a commit, a verb or markup. Those stay in the controller's `conditions` and in the
messenger's history, for whoever looks closer.
6. **The operator's time zone is a setting of the messenger**, `time-zone`, an IANA zone name. When it is
not given, the zone of the machine the messenger runs on is used.
7. **A condition without plain words** comes from a controller older than this decision, or from a module's
`notify` without a headline. It is said from its summary, with its code spans taken out. `notify`
accepts the same three fields.
3. **The plain rule.** Headline, needs, explanation and resolved line name no hash, numbered id, dotted
name (a key, a verb, a unit's file name), duration in code, clock time, date or zone, command flag,
markup or line break, and pass the content rule. **They never send the operator elsewhere**: no
"agent", no "session", and `needs` never says "by hand". `needs` is one sentence ending in a full stop.
Words that break the rule are replaced by the scope's words and reported; a condition is never refused
for its words.
4. **Whether the operator is needed is decided per condition, by its producer.** "Nothing for you to do."
is said where the mesh handles it or it passes by itself; "Needs you:" where only the operator can do
the next thing. A condition a healer gave up on (`resolver: operator` with attempts) that needed nothing
now needs the operator: its verdict becomes "Needs you:".
5. **A warning that needs nothing is not sent.** The operator channel keeps it open, its history says it
was kept quiet, and it is said the moment it needs the operator: when it becomes urgent at its bound, or
when its words change to "Needs you:". An urgent condition is always said. At this decision these kinds
need nothing while they are warnings:
- a walk waiting for its delivery's word (under four hours), or stuck at a step;
- a machine silent, not confirming a change, waiting for a push, its tools silent or unreachable, or its
network check failing or rewritten;
- a healer wanted, a listener behind, the bus being upgraded, a check that could not run, slow status,
outdated facts or instructions, a merge or build not picked up;
- a provider failing or silent, the mesh's own software restarting, an update put back on its first
machine, data not measured, and unused data the operator chose to keep.
6. **The notification offers its answers, and the operator channel performs them.**
- The holder of `operator-channel` gives each action, and **Details**, a token, and sends them with the
message. A message that offers answers is never folded into a digest.
- **The desk:** `node-notifier.send` takes actions (token and label). The dunst holder offers them
(dunst's actions: a middle click, or its context menu) and says the chosen token as its event
`action-chosen` {id, token, machine}. It is that module's event until seats publish events of their
own (to-be 46 phase 3), when it becomes the seat's.
- **The holder calls the action's verb itself**, with a why that names the operator, the channel and the
label ("the operator chose Release on the desktop notification …"), then edits the notification to say
what came of it: done, or the refusal in words. Details shows the summary, the key and since when, on
the machine that asked; a line the content rule refuses is said withheld.
- **The tier.** None of these verbs declares an authorising tier: each is a verb any granted principal
may already call, which is ADR 0234's *acknowledge*. So a desk click performs it, as ADR 0234 allows.
When to-be 46 phase 5 declares a tier for one of them, its answer goes through `authorise request` and
`answer` instead, and the desk asks for the code. Nothing here lowers ADR 0234's bar.
- **Telegram** shows the words and no buttons until it holds the `intake` seat (to-be 46 phase 6).
7. **Times are the operator's.** The body ends with since when, in the operator's time zone (the
messenger's setting `time-zone`): a clock time today, a weekday this week, a date before. A clearance is
one line: "Resolved:", the resolved line, and how long it was open. No message carries a key, an id, a
commit, a verb or markup; those stay in the controller's `conditions`, the holder's history and Details.
8. **A condition without plain words** (a controller older than this, or a module's `notify` without a
headline) is said from its summary, with its code spans taken out, and is never kept quiet. `notify`
accepts the same words.
## Consequences
- The popup of issue 320 becomes a title and two short lines, in plain words:
"openrazer delivery waiting to start". The body says that the change to openrazer is merged and built,
that it has waited 56 minutes for mesh-delivery (the module that decides when a delivery goes out) to
let it start, that there is nothing to do yet, that it becomes urgent after 4 hours, and that an agent
can start it by hand. Then "Since 12:40." in the operator's zone. When it clears, one line:
"Resolved: openrazer delivery no longer waiting, after 26 min".
- A new channel shows the same words with no formatter of its own.
- A new condition kind needs words. The controller's suite fails until it has them, the same way it fails
a summary that carries an address.
- An explanation is written when a condition is raised, and kept current on each observation. A message
already sent is not edited when the words change, which matches how the summary already behaves.
- The fallback for a condition without plain words is still the summary, with its identifiers. It lasts
only until the controller that makes the words runs. A module's `notify` keeps it until the module gives
a headline.
- The words are English, as everything else is. A second language is not decided here.
- **The popup of issue 320 is not shown.** A walk waiting under four hours needs nothing; it is in
`conditions` and in `operator-channel.history`, marked as kept quiet. Past four hours it is urgent and
reads "openrazer delivery waiting to start", then "Needs you: start it, or stop it." and why, then "Since
12:40.", with the answers Start, Stop and Details. The operator chooses Start, and the notification says
"Start: done."
- A held delivery offers Release and Stop; a failed service, Restart; failed units nobody manages, and data
that shrank or went, Silence for a week; a module whose account waits for a new login says so, which no
button can do.
- **Some "Needs you:" have no button yet**, and say what to do in words: approving a retirement, deciding
where a module's data lives, undoing a catalogue change, restarting the controller, putting the bus back.
Their verbs either take what the operator must see first (`retire approve` takes the set it approves) or
need a terminal on another machine. Each is a next step: an action where a verb exists, the authorising
path of to-be 46 phase 5 where it must be proven.
- An answer is performed by the messenger's own grant. Its manifest invokes exactly the verbs its actions
name, and a new action names a new verb there. The verb's own record (a hand-act, a delivery's
transition) says who chose it, through the why.
- dunst waits for the answer in its bundle. A restart of that bundle ends the waits it held: the
notification stays and its answers do nothing then. A notification closed or expired says nothing.
- A new condition kind needs words, including its verdict. The controller's suite fails until it has them.
- The words are English, as everything else is.
## How it is checked
- **The controller** (`mesh-controller`):
- `internal/conditions/plain_test.go`:
- the plain rule refuses each shape it names and passes the words conditions are made of;
- a condition carries a producer's own words, else its kind's, else its scope's;
- words that break the rule are replaced and reported;
- the events carry all three fields;
- an escalated condition says it needs the operator, once.
- `cmd/mesh-controller/plain_words_test.go`:
- the real notifications of issue 320 are raised from the same facts, and each shows its summary
unchanged and its plain words: a delivery waiting (warning, urgent, many modules), a module
unhealthy, failed units on a machine, a healer wanted, a delivery held;
- every registered wording is plain for a subject of every scope.
- **A lint over the whole suite** (`sayable_test.go`, beside issue 277's): every condition raised in any
test of the controller's command has plain words of its own kind or its producer's. A kind without
them, or words that break the rule, fails the suite and names the kind and its source.
- **The messenger** (`mesh-catalog`, `modules/messenger/cmd/messenger/words_test.go`):
- the four real notifications, decoded from the events a controller with plain words sends, show
exactly their title and body on the desktop, in the operator's zone, with no key, id, commit, verb or
markup, and clear in one line;
- a condition without plain words is said from its summary, without code spans;
- times are said in the operator's zone;
- a zone the machine does not know is said, not guessed.
The holder's tests check that no message carries a key.
- **Live**, once both are delivered: `operator-channel.history` lists the next messages, and their titles
are headlines. `messenger_status` names the time zone used.
- `internal/conditions/plain_test.go`: the plain rule refuses each shape it names; an explanation opens
with its verdict; words that send the operator to an agent, a session or "by hand" are refused; a
condition carries its producer's words, else its kind's, else its scope's, and the events carry them
all; an escalated condition turns "Nothing for you to do." into "Needs you:", once.
- `cmd/mesh-controller/plain_words_test.go`: the real notifications of issue 320, raised from the same
facts, each with its exact verdict, explanation and actions — a walk waiting under its bound (nothing)
and past it (Start, Stop, and the plan each calls), a module unhealthy (Restart, the unit and the
account's manager), relogin needed (no button), failed units (Silence), a healer wanted (nothing), a
delivery held (Release, Stop); and every registered wording plain for a subject of every scope.
- **A lint over the whole suite** (`sayable_test.go`): every condition raised in any test has words of
its producer or its kind, and they pass the rule.
- **The messenger** (`mesh-catalog`, `modules/messenger`), `words_test.go`: the same notifications decoded
from the events the controller sends: a warning that needs nothing is not shown and is kept as quiet in
the history; each shown one has its exact title, body and answers; choosing an answer calls exactly the
seat verb and arguments its condition names, with the operator's why, and edits the notification; the
clearance is one line. A quiet warning is said when it becomes urgent. Details are shown where they were
asked. The holder's tests fail a message that carries a key.
- **dunst** (`mesh-catalog`, `modules/dunst`), `actions_test.go`: `send` with actions answers the id before
the answer comes, asks `notify-send` for each action and to wait, says the chosen token as
`action-chosen` with the id and machine, says nothing for a notification closed without an answer or with
an answer it did not offer, and refuses actions it cannot carry.
- **Live**, once delivered: `operator-channel.history` shows warnings kept quiet; the next held delivery
shows Release and Stop on the desk; choosing one is in `mesh-delivery.show` with the operator's why.
## References
- [Issue 320](../04-ISSUES/320-the-operator-could-not-read-the-meshs-notifications/00-report.md)
- [Issue 277](../04-ISSUES/277-one-unanswered-question-was-an-urgent-alert-nobody-could-read/00-report.md)
(a summary held to the content rule; this record's lint sits beside that one)
- [ADR 0227](0227-the-core-holds-nine-rules-each-checked-and-is-built-to-them-in-six-phases.md) (rule 6:
the mesh says when it is wrong), [ADR 0234](0234-the-mesh-holds-a-conversation-with-its-operator.md)
(the channels, the router, the content rule)
- [To-be 45](../03-DESIGN/01-to-be/45-a-core-that-cannot-fail-silently.md) §2 and §5
- [ADR 0234](0234-the-mesh-holds-a-conversation-with-its-operator.md) (the channels, the content rule, the
tiers), [ADR 0227](0227-the-core-holds-nine-rules-each-checked-and-is-built-to-them-in-six-phases.md)
(rule 6: the mesh says when it is wrong), [ADR 0239](0239-a-delivery-is-owned-by-the-mesh-delivery-module-and-runs-from-commit-to-delivered.md)
(a walk waits for its delivery's word)
- [To-be 45](../03-DESIGN/01-to-be/45-a-core-that-cannot-fail-silently.md) §2 and §5;
[to-be 46](../03-DESIGN/01-to-be/46-the-conversation-with-the-operator.md) §11 and §13
@@ -128,7 +128,8 @@ cleared on the first that does not. Designed in [to-be 48](48-a-module-says-how-
| subject | scope, id, and the machine it concerns when there is one |
| severity | `urgent` (needs the operator now) or `warning` (when they can) — two levels, no more |
| summary | one line in the mesh's words, for whoever looks closer: it may name plans, commits and verbs |
| headline, explanation, resolved | what the operator reads, in plain words (ADR 0253): a few words naming the thing and what is wrong; one or two sentences on what happened, what it means and whether to act; the line said when it clears |
| headline, needs, explanation, resolved | what the operator reads, in plain words (ADR 0253): a few words naming the thing and what is wrong; the one thing the operator can do themselves, or nothing; the explanation, opened by its verdict ("Nothing for you to do." or "Needs you:" and that thing); the line said when it clears |
| actions | the answers a notification offers (Release, Stop, Start, Restart, Silence for a week), each a label and the seat verb the operator channel calls when it is chosen (ADR 0253) |
| evidence | the newest observations, at most ten, each with its time |
| source | the signals-table row, probe or event that raised it |
| raised, last observed | times; and how many observations since raised |
@@ -138,15 +139,16 @@ cleared on the first that does not. Designed in [to-be 48](48-a-module-says-how-
| epoch | the controller lease epoch that last wrote it |
*Added 2026-10-08, [ADR 0253](../../02-DECISIONS/0253-a-condition-says-itself-to-the-operator-in-plain-words-beside-its-summary.md):*
**a condition says itself to the operator in plain words, beside its summary.** The headline, explanation
and resolved line are made where the condition is made: by the producer when it knows more than the
subject (a walk's modules, a failed unit's name, a hand-act's cause), otherwise by the wording registered
for the kind, otherwise from the scope, which is reported. All three are held to the plain rule when the
controller takes an observation: no hash, numbered id, dotted name, duration in code, clock time, flag,
markup or line break, and the content rule besides. Words that break the rule are replaced by the scope's
words, never refused. A condition a healer gave up on says in its explanation that it needs the operator.
Checked by the plain rule's test and by a lint over every condition the controller's suite raises, which
fails a kind without words.
**a condition says itself to the operator in plain words, beside its summary, and says whether the operator
is needed.** The headline, needs, explanation, resolved line and actions are made where the condition is
made: by the producer when it knows more than the subject (a walk's modules, a failed unit's name, a
hand-act's cause), otherwise by the wording registered for the kind, otherwise from the scope, which is
reported. The explanation opens with its verdict. All of it is held to the plain rule when the controller
takes an observation: no hash, numbered id, dotted name, duration in code, clock time, flag, markup or line
break; nothing that sends the operator to an agent or a session; and the content rule besides. Words that
break the rule are replaced by the scope's words, never refused. A condition a healer gave up on that needed
nothing now needs the operator. Checked by the plain rule's test and by a lint over every condition the
controller's suite raises, which fails a kind without words.
**The life of one:**
@@ -299,10 +301,14 @@ section; to-be 46 §13 builds what follows from it.
severity, the one-line summary, since when, and the verb that shows more. **Deduplicated by the key.**
*Amended 2026-10-08, [ADR 0253](../../02-DECISIONS/0253-a-condition-says-itself-to-the-operator-in-plain-words-beside-its-summary.md):*
a message reads in one glance. Its title is the condition's headline, with "Urgent:", "Still open:" or
"Now urgent:" before it where that applies. Its body is the explanation and since when, in the
operator's time zone (the messenger's setting `time-zone`). It carries no key, id, commit, verb or
markup: those stay in `conditions` and in the holder's history. A clearance is one line, the resolved
line and how long it was open. A digest lists titles. Still deduplicated by the key.
"Now urgent:" before it where that applies. Its body is the explanation, opened by its verdict, and since
when, in the operator's time zone (the messenger's setting `time-zone`). It offers the condition's
actions and Details; the desk takes the answer (`node-notifier.send` gains actions, and the dunst holder
says the chosen one as its event), and the holder calls the action's verb with the operator's why. It
carries no key, id, commit, verb or markup: those stay in `conditions`, the holder's history and
Details. **A warning that needs nothing is not sent**: it is kept, and said when it becomes urgent or
needs the operator. A clearance is one line, the resolved line and how long it was open. A digest lists
titles, and a message that offers answers is never in one. Still deduplicated by the key.
- **When:** on `condition-raised`; once more if still open after 1 hour (urgent) or 12 hours
(warning); on `condition-cleared`, by editing the first message where the channel can. A silenced
condition sends nothing. Urgent goes to both channels; warning to the desktop notifier when the
@@ -2,8 +2,9 @@
layer: to-be
status: designed
code: []
updated: 2026-10-06
updated: 2026-10-08
decisions:
- 02-DECISIONS/0253-a-condition-says-itself-to-the-operator-in-plain-words-beside-its-summary.md
- 02-DECISIONS/0234-the-mesh-holds-a-conversation-with-its-operator.md
- 02-DECISIONS/0227-the-core-holds-nine-rules-each-checked-and-is-built-to-them-in-six-phases.md
---
@@ -448,6 +449,11 @@ needs the lost factor's code. So:
- **`node-notifier.send` gains actions** (token, label). It still answers at once; the dunst holder
listens for the chosen action and emits it as an event on its node seat.
*Amended 2026-10-08, [ADR 0253](../../02-DECISIONS/0253-a-condition-says-itself-to-the-operator-in-plain-words-beside-its-summary.md):*
built ahead of the router for the answers a condition offers. Until seats publish events of their own
(phase 3) the chosen token is the dunst module's event `action-chosen` {id, token, machine}, and the
output seat's holder takes it and calls the action's verb. Only verbs that declare no tier are offered
so; one that declares a tier waits for phase 5.
- The router's desktop adapter, holding `channel/desktop` and `intake/desktop`, turns that into a
`choice` envelope.
- **For `text`, `number`, `date` and codes**, the notification's single action opens the
@@ -1,7 +1,7 @@
---
status: located
opened: 2026-10-08
located-in: [mesh-controller internal/conditions (the condition's words), mesh-controller cmd/mesh-controller (every producer of a condition), mesh-catalog modules/messenger (compose, the digest)]
located-in: [mesh-controller internal/conditions (the condition's words), mesh-controller cmd/mesh-controller (every producer of a condition), mesh-catalog modules/messenger (compose, the digest, the answers), mesh-catalog modules/dunst (the desk's actions)]
fixed-by: novox/mesh-controller PR #141, novox/mesh-catalog PR #126
amended-design: 03-DESIGN/01-to-be/45-a-core-that-cannot-fail-silently.md
---
@@ -35,6 +35,12 @@ The same shape held for every open condition on the mesh that day: a module unhe
units on a workstation, a cause repaired by hand thirty-five times, five deliveries held past their bound,
each with `mesh-delivery.show <repository>@<commit>` in its line.
**A first fix was read, and still failed.** Reworded in plain words, the same popup said "… Nothing to do
yet; it becomes urgent after 4 hours. To start it now, have an agent start it by hand." The operator asked:
*what does that notification mean, I need to do something? I need to open a session and tell it to deliver
it?* It did not say plainly whether they were needed; it was shown although it needed nothing; and the only
thing it offered was a session.
## Cause
**The condition had one line of words, and it served two readers.** To-be 45 §2 gave a condition one
@@ -48,6 +54,12 @@ Issue 277 had already found the narrower half: a summary carried detail the cont
controller now holds every summary to that rule. Identifiers, verbs and markup pass that rule — they are
not addresses, paths or secrets — so they reached the operator untouched.
**Nothing said whether the operator was needed, or let them answer.** No field of a condition said whether
the operator was needed, so every warning was shown, and an explanation could imply an instruction. The
verbs that answer the common cases existed (release or stop a delivery, start a waiting walk, restart a
service, silence a condition), but the desk could not take an answer: `node-notifier.send` had no actions,
and the conversation design that adds them (to-be 46) was not built. The one way to act was a session.
**The message added its own detail.** The messenger's compose added `about:`, `since:` in UTC, `key:` and
`more:` lines to every message, and a digest added each key in brackets. A clearance was the whole
summary again behind "CLEARED after …".
@@ -55,16 +67,19 @@ summary again behind "CLEARED after …".
## Fix
[ADR 0253](../../02-DECISIONS/0253-a-condition-says-itself-to-the-operator-in-plain-words-beside-its-summary.md):
every condition carries, beside its summary, a **headline**, an **explanation** and a **resolved line**
in plain words, made where the condition is made and held to a checked plain rule; every channel shows
those and says the time in the operator's zone; a clearance is one line.
every condition carries, beside its summary, a **headline**, what it **needs** of the operator, an
**explanation** opened by its verdict ("Nothing for you to do." or "Needs you:"), a **resolved line** and
its **actions**, made where the condition is made and held to a checked plain rule. A warning that needs
nothing is not sent. The desk offers the actions, and the operator channel's holder performs the chosen one
through its seat verb. Every channel says the time in the operator's zone; a clearance is one line.
- novox/mesh-controller: the condition's three new fields, the plain rule, a wording for every kind the
controller raises, the richer words at the producers that know more (a walk's modules, a unit's name, a
hand-act's cause, a delivery's repository), and a lint over every condition the suite raises.
- novox/mesh-catalog: the messenger shows the words, says since when in the operator's zone (the new
setting `time-zone`), says a clearance in one line, names nothing but titles in a digest, and lets a
module give the same words through `notify`.
- novox/mesh-catalog: the messenger shows the words, keeps quiet what needs nothing, offers and performs
the answers, says since when in the operator's zone (the new setting `time-zone`), says a clearance in
one line, names nothing but titles in a digest, and lets a module give the same words through `notify`;
dunst's `send` takes actions and says the chosen one as its event.
## How it is checked
@@ -74,5 +89,6 @@ shows them.
## Design
To-be 45 §2 gains the three fields, and §5's message is no longer the key, kind and verb but the
headline, the explanation and since when.
To-be 45 §2 gains the fields, and §5's message is no longer the key, kind and verb but the headline, the
explanation with its verdict, since when and the answers; a warning that needs nothing is not sent. To-be
46 §11 notes the desk's actions built ahead of its router.