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:
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user