ADR 0253: say whether the operator is needed, keep quiet what needs nothing, answer from the notification
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:
+5
-4
@@ -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
|
||||
|
||||
+170
-99
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user