desk-channel: run as its own account, and show a link's code only where a notifier heard unpaused will show it, kept in no history

The review of 2026-10-09 (H4). The desk ran in the operator's runtime, where agents run; it now runs as
the account desk-channel, as telegram does. A link's code is shown only on a named machine whose notifier
said in the last five minutes that it is not paused, and is sent secret, so the notifier keeps it out of
the history every caller of the verbs reads; while no such machine exists the desk says it cannot send,
and the router starts no link.
This commit is contained in:
2026-10-09 17:08:17 +02:00
parent c161521e49
commit 0284204989
5 changed files with 204 additions and 10 deletions
+17 -4
View File
@@ -20,10 +20,23 @@ router sends warnings to the channel that reaches the operator away.
## Where it runs
Held once for the mesh, on the same machine as the router, which is where it is assigned. A message the
router marks secret (a link's code) is shown and kept nowhere of this module's; the desktop's own notifier
history may still hold it for its length (20 notifications). It asks each machine's notifier through the
mesh: nothing new runs on the machines.
Held once for the mesh, on the same machine as the router, which is where it is assigned. **It runs as the
account `desk-channel`, in a runtime of its own, on a bus account of its own** (`runs-as`), as telegram does:
what it shows and the answers it says are out of reach of the operator's account, which agents run as. It asks
each machine's notifier through the mesh: nothing new runs on the machines.
## A link's code (the review of 2026-10-09)
- **It is shown only where it is seen.** Each machine's notifier says whether it is paused (`dunst.paused`, at
least every two minutes). A message the router marks secret is shown only on a named machine heard unpaused in
the last five minutes; while there is none, it is said to the router as failed and the desk says it cannot
send, so the router starts no link. A code held by a paused notifier would appear later, to whoever is at the
screen then.
- **It is kept nowhere.** Not in this module's state, and sent `secret` to the notifier, which sends it
transient in the category `mesh.secret`: dunst's rules keep it out of its history, and
`node-notifier.history`, which every caller of the mesh's verbs may read, leaves it out.
- **What remains**: while it is on screen, a program of the operator's account on that X display can read it
(X11 isolates no client from another). It is shown for as long as the router's code lives.
## Tools
+74 -4
View File
@@ -31,6 +31,19 @@ import (
// Kind is this channel's kind on both benches.
const Kind = "desktop"
// Account is the account this module runs as, in a runtime of its own (runs-as).
const Account = "desk-channel"
// PausedFresh is how old a notifier's word on whether it is paused may be: it says it every two minutes, so
// two missed is a notifier stopped or a machine away.
const PausedFresh = 5 * time.Minute
// pauseSeen is what a machine's notifier last said of its pause, and when.
type pauseSeen struct {
paused bool
at time.Time
}
// StandingEvery is how often the channel says whether it can send, at least.
const StandingEvery = 5 * time.Minute
@@ -65,6 +78,7 @@ type Desk struct {
said asks.Standing
saidAt time.Time
acted map[string]bool
pause map[string]pauseSeen
}
func (d *Desk) init() {
@@ -73,6 +87,9 @@ func (d *Desk) init() {
if d.last == nil {
d.last, d.clickedOn, d.acted = map[string]string{}, map[string]string{}, map[string]bool{}
}
if d.pause == nil {
d.pause = map[string]pauseSeen{}
}
if d.Now == nil {
d.Now = time.Now
}
@@ -105,7 +122,17 @@ func (d *Desk) Take(subject string, body json.RawMessage) error {
return nil
}
machines := d.Machines()
if m.Reply != "" {
if m.Secret {
// Shown only where the notifier said lately it is not paused (the review of 2026-10-09): one held back
// would appear later, to whoever is at the screen then.
seeing, why := d.seeing()
if len(seeing) == 0 {
f := asks.Failed{Handle: m.Handle, Why: "not shown: " + why, At: d.Now()}
return d.Publish(asks.IntakeSubject(asks.FailedWhat, Kind), f, "failed."+m.Handle+"."+strconv.FormatInt(d.Now().UnixNano(), 10))
}
machines = seeing
}
if m.Reply != "" && !m.Secret {
// An answer to a click is shown where the click was made.
d.mu.Lock()
on, ok := d.clickedOn[m.Reply]
@@ -175,6 +202,11 @@ func (d *Desk) show(machine string, m asks.Message, replace int) (int, error) {
if replace != 0 {
args["replace_id"] = replace
}
if m.Secret {
// Transient and secret: the notifier keeps it out of the history every caller of the verbs reads.
args["secret"] = true
args["transient"] = true
}
if len(m.Buttons) > 0 {
var actions []map[string]string
for _, b := range m.Buttons {
@@ -290,12 +322,49 @@ func (d *Desk) Started(machine string) int {
return n
}
// Standing is whether the desk can send: machines are named to show on.
// Paused takes a notifier's word on whether it is paused (dunst's event `paused`), from the event's own node.
func (d *Desk) Paused(machine string, paused bool) {
d.init()
d.mu.Lock()
defer d.mu.Unlock()
d.pause[machine] = pauseSeen{paused: paused, at: d.Now()}
}
// seeing is the named machines whose notifier said lately that it is not paused, and why none is when none is.
func (d *Desk) seeing() ([]string, string) {
machines := d.Machines()
if len(machines) == 0 {
return nil, "no machine to show it on: the setting desktop-machines names none"
}
now := d.Now()
d.mu.Lock()
defer d.mu.Unlock()
var out, not []string
for _, m := range machines {
p, heard := d.pause[m]
switch {
case !heard || now.Sub(p.at) > PausedFresh:
not = append(not, m+" (its notifier not heard from lately)")
case p.paused:
not = append(not, m+" (notifications paused)")
default:
out = append(out, m)
}
}
if len(out) == 0 {
return nil, "notifications are paused on the desk, or its notifier was not heard from lately, on every " +
"machine named: " + strings.Join(not, ", ")
}
return out, ""
}
// Standing is whether the desk can send: a named machine whose notifier said lately that it is not paused
// (the review of 2026-10-09). The router shows no link's code while the desk is not ready.
func (d *Desk) Standing() asks.Standing {
d.init()
s := asks.Standing{At: d.Now()}
if len(d.Machines()) == 0 {
s.Why = "no machine to show it on: the setting desktop-machines names none"
if _, why := d.seeing(); why != "" {
s.Why = why
return s
}
s.Ready = true
@@ -338,6 +407,7 @@ func (d *Desk) Status() map[string]any {
"it proves nothing about who clicked: any process of the operator's account can choose an action, so it answers acknowledgements only",
"an answer is taken only on a machine its message was shown on; a message with buttons stays until dismissed",
"a notifier that started again is shown again every open message with buttons it showed",
"it can send while a named machine's notifier said lately that it is not paused; a link's code is shown only on such a machine, kept nowhere, and sent secret so the notifier keeps it out of its history",
}}
if s.Why != "" {
out["why_not"] = s.Why
@@ -155,7 +155,7 @@ func TestItPromisesNoVerifiedSender(t *testing.T) {
t.Errorf("the desk promises %s", c)
}
}
if strings.Join(m.Consumes, ",") != AnswerEvent+","+StartedEvent {
if strings.Join(m.Consumes, ",") != AnswerEvent+","+StartedEvent+","+PausedEvent {
t.Errorf("consumes %v", m.Consumes)
}
}
@@ -163,6 +163,7 @@ func TestItPromisesNoVerifiedSender(t *testing.T) {
// A message the router marks secret (a link's code) is shown and kept nowhere of this module's.
func TestASecretMessageIsShownAndNotKept(t *testing.T) {
d, shown, _, store := newDesk(t, "laptop")
d.Paused("laptop", false)
take(t, d, asks.Send, asks.Message{Handle: "code", Title: "Link a Telegram account as you", Body: "123 456", Secret: true})
if len(*shown) != 1 {
t.Fatal("not shown")
@@ -171,3 +172,90 @@ func TestASecretMessageIsShownAndNotKept(t *testing.T) {
t.Error("the code was kept")
}
}
// The review of 2026-10-09 (H4, C4): a link's code is shown only on a machine whose notifier said lately that
// it is not paused — a code held back by a paused notifier appears later, to whoever is at the screen then —
// and is sent secret, so the notifier keeps it out of the history every caller of the verbs reads. While no
// named machine is heard unpaused, the desk says it cannot send.
func TestASecretIsShownOnlyWhereTheNotifierIsHeardUnpausedAndNeverKept(t *testing.T) {
d, shown, said, store := newDesk(t, "laptop", "desk2")
now := d.Now()
if s := d.Standing(); s.Ready || !strings.Contains(s.Why, "not heard") {
t.Fatalf("ready before any notifier said whether it is paused: %+v", s)
}
d.Paused("laptop", false)
if s := d.Standing(); !s.Ready {
t.Fatalf("not ready with laptop heard unpaused: %+v", s)
}
code := asks.Message{Handle: "code", Title: "Link a Telegram account as you", Body: "123 456", Secret: true}
take(t, d, asks.Send, code)
if len(*shown) != 1 || (*shown)[0].machine != "laptop" || (*shown)[0].args["secret"] != true {
t.Fatalf("the code was shown as %+v", *shown)
}
if _, kept := store["code"]; kept {
t.Error("the code was kept")
}
// Paused: not shown anywhere, said as failed, and the desk is not ready.
d.Paused("laptop", true)
take(t, d, asks.Send, code)
if len(*shown) != 1 {
t.Fatalf("the code was shown on a paused notifier: %+v", *shown)
}
if f := said[asks.IntakeSubject(asks.FailedWhat, Kind)]; len(f) != 1 || !strings.Contains(string(f[0]), "paused") {
t.Errorf("not said as failed: %s", f)
}
if s := d.Standing(); s.Ready || !strings.Contains(s.Why, "paused") {
t.Errorf("ready while paused: %+v", s)
}
// Heard unpaused, but longer ago than PausedFresh: not known now.
d.Paused("laptop", false)
d.Now = func() time.Time { return now.Add(PausedFresh + time.Second) }
if s := d.Standing(); s.Ready {
t.Errorf("ready on a word older than %s: %+v", PausedFresh, s)
}
// A machine not named is never one to show on, whatever it says.
d.Paused("stranger", false)
if s := d.Standing(); s.Ready {
t.Error("a machine not named made the desk ready")
}
// A message that is not secret still goes to every named machine, paused or not.
take(t, d, asks.Send, asks.Message{Handle: "m9", Title: "x"})
if len(*shown) != 3 {
t.Errorf("an ordinary message was held back: %+v", *shown)
}
}
// It runs as its own account (the review of 2026-10-09, H4): what it shows and the answers it says are its
// own, out of every agent's reach, as telegram's are.
func TestItRunsAsItsOwnAccount(t *testing.T) {
raw, err := os.ReadFile(filepath.Join("..", "..", "module.json"))
if err != nil {
t.Fatal(err)
}
var m struct {
RunsAs string `json:"runs-as"`
Own map[string]string `json:"own-secrets"`
Owner string `json:"secrets-owner"`
Resources []map[string]any
}
if err := json.Unmarshal(raw, &m); err != nil {
t.Fatal(err)
}
if m.RunsAs != Account || m.Owner != Account || m.Own["broker"] == "" {
t.Fatalf("runs as %q, owner %q, secrets %v", m.RunsAs, m.Owner, m.Own)
}
var account, state, settings bool
for _, r := range m.Resources {
switch r["id"] {
case "account":
account = r["type"] == "user" && r["name"] == Account && r["shell"] == "/usr/bin/nologin"
case "state":
state = r["owner"] == Account && r["mode"] == "0700"
case "settings":
settings = r["owner"] == Account && r["mode"] == "0600"
}
}
if !account || !state || !settings {
t.Errorf("the account %v, its state %v, its settings %v", account, state, settings)
}
}
@@ -22,6 +22,7 @@ const Worker = "SEAT_CHANNEL_DESKTOP_worker"
const (
AnswerEvent = "dunst.action-chosen"
StartedEvent = "dunst.started"
PausedEvent = "dunst.paused"
)
type busStore struct{}
@@ -107,6 +108,7 @@ func run(d *Desk) {
var body struct {
Token string `json:"token"`
Machine string `json:"machine"`
Paused *bool `json:"paused"`
}
_ = json.Unmarshal(e.Body, &body)
// The machine is the event's own node, said by the runtime: the body's word is only a hint.
@@ -117,6 +119,12 @@ func run(d *Desk) {
switch e.Key {
case StartedEvent:
d.Started(machine)
case PausedEvent:
// Only from the event's own node: a word about another machine is no word at all.
if body.Paused != nil && e.Node != "" {
d.Paused(e.Node, *body.Paused)
d.SayStanding()
}
case AnswerEvent:
if body.Token != "" {
d.Chosen(e.Headers["x-event-id"], machine, body.Token)
+16 -1
View File
@@ -2,6 +2,7 @@
"module": "desk-channel",
"version": "1",
"slug": "desk",
"runs-as": "desk-channel",
"claims": [
{
"name": "channel",
@@ -25,7 +26,8 @@
],
"consumes": [
"dunst.action-chosen",
"dunst.started"
"dunst.started",
"dunst.paused"
],
"invokes": [
"seat:node-notifier.send"
@@ -36,14 +38,26 @@
"ttl-seconds": 2592000
}
],
"own-secrets": {
"broker": "${dir:state}/broker"
},
"secrets-owner": "desk-channel",
"tools": [
"desk_channel_status"
],
"resources": [
{
"id": "account",
"type": "user",
"name": "desk-channel",
"shell": "/usr/bin/nologin",
"home": "/var/lib/desk-channel"
},
{
"id": "state",
"type": "directory",
"mode": "0700",
"owner": "desk-channel",
"place": "."
},
{
@@ -51,6 +65,7 @@
"type": "file",
"path": "${dir:state}/settings.json",
"mode": "0600",
"owner": "desk-channel",
"merge": "json",
"content": "{\n \"desktop-machines\": []\n}\n"
}