diff --git a/00-META/glossary.md b/00-META/glossary.md index 3ed916a0..5c2493bc 100644 --- a/00-META/glossary.md +++ b/00-META/glossary.md @@ -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 diff --git a/02-DECISIONS/0253-a-condition-says-itself-to-the-operator-in-plain-words-beside-its-summary.md b/02-DECISIONS/0253-a-condition-says-itself-to-the-operator-in-plain-words-beside-its-summary.md index 6733c7ad..5ba0c219 100644 --- a/02-DECISIONS/0253-a-condition-says-itself-to-the-operator-in-plain-words-beside-its-summary.md +++ b/02-DECISIONS/0253-a-condition-says-itself-to-the-operator-in-plain-words-beside-its-summary.md @@ -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 `` `` 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 ``". 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 ``". + - **needs**: one sentence, the one thing the operator can do themselves — "release it, or stop it.", + "log out of every session on `` 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 diff --git a/03-DESIGN/01-to-be/45-a-core-that-cannot-fail-silently.md b/03-DESIGN/01-to-be/45-a-core-that-cannot-fail-silently.md index 9418fb20..ab3df3b8 100644 --- a/03-DESIGN/01-to-be/45-a-core-that-cannot-fail-silently.md +++ b/03-DESIGN/01-to-be/45-a-core-that-cannot-fail-silently.md @@ -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 diff --git a/03-DESIGN/01-to-be/46-the-conversation-with-the-operator.md b/03-DESIGN/01-to-be/46-the-conversation-with-the-operator.md index 578efbe0..29d5606f 100644 --- a/03-DESIGN/01-to-be/46-the-conversation-with-the-operator.md +++ b/03-DESIGN/01-to-be/46-the-conversation-with-the-operator.md @@ -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 diff --git a/04-ISSUES/320-the-operator-could-not-read-the-meshs-notifications/00-report.md b/04-ISSUES/320-the-operator-could-not-read-the-meshs-notifications/00-report.md index 3eca0a54..94e0c2c0 100644 --- a/04-ISSUES/320-the-operator-could-not-read-the-meshs-notifications/00-report.md +++ b/04-ISSUES/320-the-operator-could-not-read-the-meshs-notifications/00-report.md @@ -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 @` 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.