From dd149a72c06e0119730fbed3c62a20ef690a219a Mon Sep 17 00:00:00 2001 From: jochen Date: Thu, 8 Oct 2026 14:40:02 +0200 Subject: [PATCH] Issue 324 and ADR 0258: a desk click performs only an acknowledgement Review found that ADR 0253 let any process at the operator's session release, stop or start a delivery in the operator's name, and that its tokens, fallback words and quiet warnings failed on the paths that go wrong. --- ...rator-in-plain-words-beside-its-summary.md | 7 + ...ment-and-an-answer-is-a-one-time-ticket.md | 120 ++++++++++++++++++ .../45-a-core-that-cannot-fail-silently.md | 8 +- .../46-the-conversation-with-the-operator.md | 6 +- .../00-report.md | 60 +++++++++ 5 files changed, 197 insertions(+), 4 deletions(-) create mode 100644 02-DECISIONS/0258-a-desk-click-performs-only-an-acknowledgement-and-an-answer-is-a-one-time-ticket.md create mode 100644 04-ISSUES/324-a-desk-click-could-answer-in-the-operators-name/00-report.md 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 5ba0c219..5ce70b7a 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 @@ -132,6 +132,13 @@ What the mesh had, on the main branches of 2026-10-08: 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. +> **The mechanism changed — 2026-10-08, by [ADR 0258](0258-a-desk-click-performs-only-an-acknowledgement-and-an-answer-is-a-one-time-ticket.md).** +> What stands: a notification offers answers and the operator channel performs them. What moved: a desk click +> performs only an acknowledgement (Details, Silence for a week) until answers are authorised; release, stop, +> start and restart are said in words. The reading of *acknowledge* below, and the promise to honour a tier +> "when one exists", were enforced by nothing and are replaced there, with one-time tokens, a verdict that is +> never weakened and a change of words that is said. + 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. diff --git a/02-DECISIONS/0258-a-desk-click-performs-only-an-acknowledgement-and-an-answer-is-a-one-time-ticket.md b/02-DECISIONS/0258-a-desk-click-performs-only-an-acknowledgement-and-an-answer-is-a-one-time-ticket.md new file mode 100644 index 00000000..3ce990be --- /dev/null +++ b/02-DECISIONS/0258-a-desk-click-performs-only-an-acknowledgement-and-an-answer-is-a-one-time-ticket.md @@ -0,0 +1,120 @@ +--- +topic: the mesh +status: accepted +date: 2026-10-08 +deciders: jochen +reconstructed: false +extends: 02-DECISIONS/0253-a-condition-says-itself-to-the-operator-in-plain-words-beside-its-summary.md +--- + +# 258. A desk click performs only an acknowledgement, an answer is a one-time ticket, and a verdict is never weakened + +## Context + +ADR 0253 was merged before its review finished +([issue 324](../04-ISSUES/324-a-desk-click-could-answer-in-the-operators-name/00-report.md)). The review found +that its §6 let a desk click release, stop or start a delivery and restart a service in the operator's +name. Any process of the operator's account can choose a desktop notification's action: dunst's own +`dunstctl action` does it, and every agent that runs as the operator can run it. A click therefore proves +that something at the operator's session chose, not that the operator did. ADR 0234 §8 holds exactly that: +"a desk click alone never authorises". ADR 0253 §6 read *acknowledge* as any verb no table had tiered, and +said a tier would be honoured once one existed. No tier field exists in the code, so nothing enforced that. + +The review also found that answer tokens never expired and could be reused or replayed; that words refused by +the plain rule weakened "Needs you" to "Nothing for you to do"; that the rule refused sound words; that a +quiet warning whose words came to need the operator was never said; and that the desk's answers died with a +ten-second notification or a restart of the notifier's bundle. + +## Considered Options + +1. **Keep §6, and ask for the operator's code at the desk** (ADR 0234's P2). That is to-be 46 phase 5: the + factor, its enrolment and recovery, and the controller's authorise verbs. Not built, and not small. + Rejected for now; it is the way the other answers come back. +2. **Trust the click, and say so in the why.** The record would still say an action was the operator's + when anything at the session could have chosen it. Rejected: failure must be loud, and a false + attribution is quiet. +3. **Offer only what is an acknowledgement in the plain sense:** Details, which performs nothing, and + silencing a condition, which ADR 0234 names as acknowledge ("silencing, announced"). Everything else is + said in words. Chosen. + +## Decision + +1. **A desk click performs only an acknowledgement**, until answers are authorised (to-be 46 phase 5): + - **Details**, which performs nothing; + - **Silence for a week**, through `conditions silence` with a why and the cause `operator-answer`. + + The operator channel's holder keeps this as an explicit allow-list and refuses everything else. A + condition's actions outside it are never offered, and a token for one is refused without a call. The + controller offers no other action. **Release, stop, start and restart are said in words**: "Needs you: + release it, or stop it." with no button. This replaces ADR 0253 §6's reading of *acknowledge* and its + promise to honour a tier. +2. **An answer is a one-time ticket.** A token is made for one message of one condition, together with the + other tokens of that message; a newer message's tokens replace them. It is bound to the machines the + message was shown on, good for a day, and used once. It is refused for a condition that cleared or is + silenced, from another machine, used, expired or unknown. One event is acted on once, by its id. **A + refused answer is said on the desk where it was chosen**, never dropped. +3. **The why says what is known**: "chosen on the desktop notification … on `` (a desk click: + whoever was at the operator's session)". A silence chosen on the desk is a decision, not a repair, and + does not count toward a healer wanted. +4. **A verdict is never weakened.** When a producer's words break the plain rule, the scope's words are said. + If the producer needed the operator, its verdict stays "Needs you:" with "read the details to see what to + do.", and its sound answers stay. +5. **The plain rule refuses no plain words.** It refuses "agent" and, in what the operator needs, "by hand", + but not "session". It reads "e.g." and "i.e." as words. A number is a hash only with letters a to f in it. + An explanation too long is cut at a word, not refused. +6. **A change of words is said.** The controller emits `condition-changed` (change `words`) when what a + condition needs or offers changes. The holder says a warning it kept quiet when its words now need the + operator, whether it learns that from the event or from a reading of the state. It also says once, after + a day, a warning still kept quiet, so nothing stays silent for ever. +7. **The desk's answers stay and come back.** A notification that offers answers stays until it is + dismissed. When the notifier's bundle starts, it says `started`, and the holder shows the open + notifications that offer answers there again, with fresh tokens. +8. **The operator's zone is always known**: the holder carries the zone data, whatever the machine has + installed. + +## Consequences + +- The delivery-waiting popup past its bound reads "Needs you: start it, or stop it. …" and offers Details + only. Starting or stopping it is not possible from the desk until phase 5. That is the honest state, + and the words say where the decision lies. +- Failed services nobody manages, and data that shrank or went, still offer Silence for a week. +- ADR 0253's other rules stand: the plain words, the verdict, the quiet rule, the time zone and the one-line + clearance. Its §6 and the consequence "the verb's own record … says who chose it" are replaced here. +- An answer chosen in a notification shown before a newer message about the same condition is refused, + and the refusal is shown. + +## How it is checked + +- **The messenger** (`mesh-catalog`, `modules/messenger/cmd/messenger/answers_test.go`): + - only an acknowledgement is answerable: Release, Restart, Start and a silence carrying another argument + are refused by the allow-list, never offered, and a token for one put in the state is refused without a + call and said on the desk; + - at most four answers, Details included; + - the same token twice is refused the second time, and the same event is acted on once; + - a token is refused from another machine, when unknown, past its day, and for a condition silenced or + cleared; + - a verb the mesh refuses is said on the notification; + - a words-only change to "Needs you" after a quiet time is said, through the event and through a reading of + the state, and a quiet warning is said once after a day; + - a notifier that started again is offered the open answers with fresh tokens, the old ones refused. + + `words_test.go` holds the real notifications of issue 320 to their exact words and answers. +- **dunst** (`modules/dunst/cmd/dunst-tools/actions_test.go`): a notification with answers is sent with no + expiry; the bundle says `started`. Checked live on the laptop on 2026-10-08: `notify-send --print-id --wait` + writes the id into a pipe at once, and "Wait timeout expired" when the notification expired. The latter is + not a token, so it says nothing. +- **The controller** (`mesh-controller`): + - `internal/conditions/plain_test.go`: the words the rule refused wrongly pass, and a long explanation is + cut; refused words keep their verdict and answers; a change of words is a `condition-changed`. + - `cmd/mesh-controller/plain_words_test.go`: the module, held-delivery and waiting conditions offer no + answer; the relogin words and the kind `relogin-needed` (ADR 0254) are held to the plain rule; a silence + with the cause `operator-answer` does not count toward a healer wanted, and one by hand still does. + +## References + +- [Issue 324](../04-ISSUES/324-a-desk-click-could-answer-in-the-operators-name/00-report.md), + [issue 320](../04-ISSUES/320-the-operator-could-not-read-the-meshs-notifications/00-report.md) +- [ADR 0253](0253-a-condition-says-itself-to-the-operator-in-plain-words-beside-its-summary.md) (§6 replaced + here), [ADR 0234](0234-the-mesh-holds-a-conversation-with-its-operator.md) §8 (the tiers, and the desk + click) +- [To-be 46](../03-DESIGN/01-to-be/46-the-conversation-with-the-operator.md) §11 and §13 phase 5 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 ab3df3b8..a23809be 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 @@ -129,7 +129,7 @@ cleared on the first that does not. Designed in [to-be 48](48-a-module-says-how- | 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, 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) | +| actions | the answers a notification offers, each a label and the seat verb the operator channel calls when it is chosen (ADR 0253); a desk click performs only an acknowledgement — Details, Silence for a week — until answers are authorised (ADR 0258) | | 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 | @@ -304,7 +304,11 @@ section; to-be 46 §13 builds what follows from it. "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 + says the chosen one as its event), and the holder calls the action's verb with a why saying it was a desk + click. *Amended 2026-10-08, [ADR 0258](../../02-DECISIONS/0258-a-desk-click-performs-only-an-acknowledgement-and-an-answer-is-a-one-time-ticket.md):* + only an acknowledgement (Details, Silence for a week) is offered or performed; each answer is a one-time + token bound to its condition and machine, good for a day; a change of words to "Needs you" is a + `condition-changed`, and a quiet warning is said once after a day. 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 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 29d5606f..83e68de5 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 @@ -452,8 +452,10 @@ needs the lost factor's code. So: *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. + output seat's holder takes it and calls the action's verb. + *Amended 2026-10-08, [ADR 0258](../../02-DECISIONS/0258-a-desk-click-performs-only-an-acknowledgement-and-an-answer-is-a-one-time-ticket.md):* + only an acknowledgement (Details, silencing) is offered so: any process of the account can choose an + action, so a click is never taken for the operator's word. Every other answer 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/324-a-desk-click-could-answer-in-the-operators-name/00-report.md b/04-ISSUES/324-a-desk-click-could-answer-in-the-operators-name/00-report.md new file mode 100644 index 00000000..7b3c8c4a --- /dev/null +++ b/04-ISSUES/324-a-desk-click-could-answer-in-the-operators-name/00-report.md @@ -0,0 +1,60 @@ +--- +status: located +opened: 2026-10-08 +located-in: [mesh-catalog modules/messenger (the answers, the quiet rule), mesh-catalog modules/dunst (the desk's actions), mesh-controller internal/conditions (the plain rule, the change of words)] +fixed-by: +amended-design: 03-DESIGN/01-to-be/45-a-core-that-cannot-fail-silently.md +--- + +# 324. A desk click could answer in the operator's name + +## Symptom + +ADR 0253 (issue 320) was merged at the operator's request before its review had finished. The +controller's part began to roll out; the build of the operator channel's holder and the desktop notifier was +published and waited for its delivery's word, so it was not yet live. The review then found: + +1. **A desk click could release, stop or start a delivery, or restart a service, in the operator's name.** + Any process of the operator's account can choose a desktop notification's action (`dunstctl action`), so + a click proves nothing about who chose. The holder then recorded "the operator chose Release". ADR 0234 §8 + says a desk click alone never authorises. ADR 0253 §6 said these verbs were acknowledgements because none + declared a tier, and that a tier would be honoured once one existed; no tier field exists in any code, so + that promise was enforced by nothing. +2. **An answer's token lived for ever.** It could be used twice, for a condition cleared or silenced, from + another machine, and a redelivered event would be acted on again. +3. **Refused words weakened the verdict.** When a producer's words broke the plain rule, the condition was + said in its scope's words, which needed nothing: "Needs you" became "Nothing for you to do", and the + answers were dropped. The rule also refused sound words: "session" (as in "log out of every session", + the words a new login needs), "e.g.", any number of seven digits or more, and an explanation one word too + long. +4. **A warning kept quiet could stay quiet for ever.** A change of its words to "Needs you" emitted no event + (only severity, resolver and silence did), the holder's reading of the state did not look for it, and + nothing said a quiet warning however long it stayed open. +5. **The desk's answers died.** A warning's notification expired in dunst's ten seconds with its answers; a + restart of the notifier's bundle ended the waits it held, and a click after it did nothing, silently. +6. The time zone depended on the machine having zone data installed. ADR 0253 said "the verb's own record + says who chose it", which was false for a restart (it takes no why). A silence chosen on the desk would + have counted as a hand repair toward a healer wanted. + +## Cause + +The decision outran its review: it read ADR 0234's *acknowledge* ("what any granted principal may already +do") as covering any verb no table had tiered, without asking what a desk click proves. The tokens, the +fallback, the event for a change of words and the notifier's restart were each designed for the path that +works, not for the path that fails. + +## Fix + +[ADR 0258](../../02-DECISIONS/0258-a-desk-click-performs-only-an-acknowledgement-and-an-answer-is-a-one-time-ticket.md) +on the branch `fix/notifications-after-review` in hq, mesh-controller and mesh-catalog. + +## How it is checked + +ADR 0258 §How it is checked: a test for every point above, including the same token twice, a cleared or +silenced token, a forged and another machine's answer, a refused verb, a words-only change after a quiet time +through the event and the reading of the state, the relogin words held to the plain rule, the fallback keeping +its verdict and answers, and the four-answer limit. + +## Design + +To-be 45 §2 and §5 and to-be 46 §11 say the desk performs only an acknowledgement.