Merge pull request 'Issue 359: a secret given reached the operator as "needs a look", and nothing said why' (#259) from issues/359-a-secret-given-reached-the-operator-as-needs-a-look into main

This commit was merged in pull request #259.
This commit is contained in:
2026-10-09 22:00:16 +00:00
2 changed files with 80 additions and 0 deletions
@@ -0,0 +1,44 @@
---
status: located
opened: 2026-10-09
located-in: [mesh-controller (cmd/mesh-controller/desk_secret.go, internal/conditions/plain.go)]
fixed-by:
amended-design:
---
# 359. A secret given reached the operator as "needs a look", and nothing said why
**Severity: medium.** The one notification meant to tell the operator that a module's own secret changed told
them nothing they could act on, and the fall back to borrowed words was silent everywhere.
## Symptom
On 2026-10-09 at 23:32 (local) the operator gave a chat module's bot token at the controller's terminal, with
the controller's `secret accept` and the value on standard input. The urgent desktop notification that followed
read, in full:
> Novox needs a look — Needs you: read the details to see what to do. The mesh noticed a problem it calls
> "secret given" with novox.
That is the scope's fallback wording ([ADR 0253](../../02-DECISIONS/0253-a-condition-says-itself-to-the-operator-in-plain-words-beside-its-summary.md)),
with the verdict kept as [ADR 0258](../../02-DECISIONS/0258-a-desk-click-performs-only-an-acknowledgement-and-an-answer-is-a-one-time-ticket.md)
requires. It names neither the module nor what was given, and "read the details" sends the operator elsewhere
for the one thing the condition exists to say: a secret changed, and silencing it is the answer when they gave
it themselves. The condition was raised on every channel on purpose — the security review of 2026-10-09 wanted
a bot token changed by somebody else to be heard of — so the words are the whole of its value.
## Two faults
1. **The condition's own words broke the plain rule.** Its producer wrote a headline, an explanation, a needs
line and a resolved line of its own; the explanation carried the clock time of the giving. The plain rule
refuses a clock time (the channel says when, in the operator's time), so the keeper replaced all of them with
the scope's words.
2. **The replacement was silent.** The keeper tells a hook of every observation said in borrowed words. A test
suite sets that hook and fails on it; the serving controller sets none, so nothing — no log line, no
condition — said that a producer's words had been refused, or why. The condition's own unit test built the
observation without passing it through a keeper, so the suite's check never saw it either.
## What is asked
- The secret-given words pass the plain rule, with a test that holds them to it through a keeper.
- Words refused at run time are never silent: the keeper logs the kind and the reason, once per process.
@@ -0,0 +1,36 @@
# Diagnosis — issue 359
## 2026-10-09 — the cause, confirmed by a test
A test in the controller's repository built the secret-given observation as the terminal does (the module, the
secret's name, "at the controller's terminal", 23:32) and held its words to the plain rule. The answer was
`explanation: a clock time or date (23:32)`. With the time removed and nothing else changed, the same words
passed, from the terminal and from a desk. The secret's name, written with a hyphen, is not refused by the rule;
the clock time alone was the cause.
The keeper's fallback (`plainly` in the controller's conditions package) then took the scope's words: "… needs a
look", and "a problem it calls" the kind with its hyphens as spaces. Since the producer had a needs line, the
verdict stayed "Needs you:" with the fallback needs, "read the details to see what to do."
## Why nothing said so
The hook told of borrowed words (`Unworded`) is set only by test suites. In the serving controller it is unset,
and the keeper replaced the words without a trace. The suite-wide check in the controller's command package
fails any condition a test raises through a keeper with words that are not plain; the secret-given test called
the producer directly and asserted on its fields, so that check never ran on it.
## The fix
In the controller's repository, on its own pull request, linked to this issue:
- The words name the module and the machine, say the secret's name as words ("telegram token"), say how it was
given, and carry no time: "New secret given for telegram on" the machine; "If you gave it, nothing else is
needed. If you did not, somebody else now holds what telegram acts with." The needs line says silencing is the
answer when the operator just gave it. Severity (urgent) and the one answer (silence for a week) are unchanged.
- A test holds the words to the plain rule and observes them through a keeper, so the suite's check covers them;
it fails against the old words with the cause above.
- With no hook set, the keeper logs which kind is said in borrowed words and why, once per kind and reason per
process, so a refusal in the running controller is visible in its journal.
Ruled out: the bound on lengths (the old needs line was 102 characters of 120), the outward content rule (no
address or secret shape in the words), and the machine name.