ADR 0260: a bar block shows a value its module keeps on the bus, and no module reads another's files
mesh/merge-gate pass: the change touches no module of the mesh's graph
mesh/repo-check pass: its merge-check.sh passed
mesh/delivery superseded: a newer head of the same pull request

The operator restated that modules talk over the bus; ADR 0255 had the bar print the
power module's file.
This commit is contained in:
jochen
2026-10-08 15:24:04 +02:00
parent 4196b711f8
commit 427b078093
3 changed files with 126 additions and 0 deletions
@@ -150,6 +150,10 @@ PLATYPUS attack, CVE-2020-8694), which is why the kernel makes it root's. No per
has no way to condition a resource on another seat (the bar) being held on the machine, and building
one for a process that reads a few kernel files every five seconds is not worth a new mechanism now.
> **Extended — 2026-10-08, by [ADR 0260](0260-a-bar-block-shows-a-value-its-module-keeps-on-the-bus-and-no-module-reads-anothers-files.md).** Modules talk over the bus. The bar no longer
> prints the power module's file: the power module keeps the draw as its state on the bus, and the bar
> is granted the read. A block may `show` such a state (§5).
**10. The memory block shows what programs hold,** not counting the cache. That is the figure
`memory-pressure` judges the same machine by.
@@ -0,0 +1,114 @@
---
topic: what runs on it
status: accepted
date: 2026-10-08
deciders: jochen
reconstructed: false
extends: 02-DECISIONS/0255-the-bar-takes-blocks-as-data-from-the-module-that-knows-the-hardware-placed-where-the-machine-has-it.md
---
# 260. A bar block shows a value its module keeps on the bus, and no module reads another's files
## Context
[ADR 0255](0255-the-bar-takes-blocks-as-data-from-the-module-that-knows-the-hardware-placed-where-the-machine-has-it.md)
gave the bar its power draw this way (§9):
- the power module's root sampler wrote one line to a file under its own runtime directory;
- the bar's block ran a command of the power module that printed that file.
That is two modules talking through a file. On 2026-10-08 the operator restated a core principle:
all communication between modules goes over the bus (NATS). The bus is where grants say who may say
what, where an exchange can be seen, and what keeps working when a module is replaced. A file between
two modules has none of that. It also binds each module to the other's paths.
What the mesh already had:
- **A module keeps state on the bus** ([ADR 0201](0201-a-module-keeps-its-current-state-in-key-value-buckets-it-declares-and-reaches-through-the-runtime.md)).
It declares the state under `state`. Another module declares what it reads under `reads`, as
`<module>.<name>`. The runtime refuses anything not issued, and a reader may never write.
- **A module's bundle reaches the bus through the node's runtime** (ADR 0198). The runtime tells it
which machine it is on.
- **The energy counter is root's** (ADR 0255 §9). The bundle runs as the operator's account and cannot
read it. The root process can read it and cannot reach the bus.
## Considered Options
1. **The power module publishes the draw as state, and the bar's bundle watches it.** The bar's block
shows a value the contributor keeps on the bus. Chosen.
2. **An event every five seconds.** Rejected on bus load and meaning. A reading is a current value that
is replaced every five seconds, and nobody needs the earlier ones. An event is history: each one is
kept in a stream until its age runs out, and a bar that starts later still has no value until the
next one arrives. State keeps one value per machine, and a reader sees it at once.
3. **Keep the file, and make it the bar's.** Rejected: the power module would write into the bar's
directory. That is a path in another module's territory, which ADR 0210 already refuses.
## Decision
**1. Modules talk over the bus, never through each other's files.** No module names a path under
`/run` or `/var/lib` that another module's manifest places. Within one module, parts may pass a file:
its root process and its bundle are one module.
**2. A block may show a value its module keeps on the bus.** This adds `shows: state` to ADR 0255 §5:
- options `state`, naming the state as `<module>.<name>`;
- option `key`, the machine's own name when absent;
- the value is an object, and its `text` is what is shown.
A module offers only state it keeps itself. A block naming another module's state, or one the
contributor does not keep, is refused at registration. The holder's template sees `machine`, the node
it renders for, so the key needs no setting.
**3. The holder is granted the read, without naming the contributor.** The controller derives the
holder's reads per machine: every state that the modules on the holder's machine offer in pieces of a
kind its seat receives as data. The bar's manifest names no power module. A bar on a machine without
the power module reads nothing of it. The contributor may write its own state, and the holder may only
read it, as ADR 0201 already grants.
**4. The power draw is the power module's state `draw`, keyed by the machine.** It holds one value
per machine, with these fields:
- `text`, the line shown;
- `watts`;
- `measures`: `machine`, `cpu`, `cpu+gpu` or `gpu`;
- `at`, when it was measured.
A value lives 30 seconds. The root sampler leaves each reading in the module's own runtime directory.
The module's bundle puts a fresh reading that measured something; a stale or empty one is not put. On
four machines that is under one message a second.
**5. The bar shows a bus value in its own way.** For i3status-rust:
- the bar's bundle watches each state its bars name;
- it leaves the value's text in the bar module's own runtime directory;
- the block runs the bar module's own command, which prints the text, or nothing when it is more than
30 seconds old, so the block hides.
Another bar module would show the same state in its own way.
**6. The battery block stays the bar's own.** i3status-rust's battery block reads the kernel's
power-supply class itself. That is the bar reading the machine, not another module, and it is allowed.
## Consequences
- ADR 0255 §9's file between the power module and the bar is gone. Its sampler, its root-only counter
and its honest labels stand.
- The `command` kind of block stays, for a bar's own commands. A module offering a value to the bar
offers state.
- The derived read needs the controller that derives it. Until it runs, the bar's watch is refused by
the runtime. The bar's bundle tries again every half minute, and the block stays hidden.
- **What got harder:** a value from the bus passes through two bundles and the runtime, so it takes
longer to arrive than a file read. It arrives within one sampling interval.
## How it is checked
| Rule | Checked by |
|---|---|
| No module names a path under `/run` or `/var/lib` that another module's manifest places | the catalogue's `checks/paths`, run by its `merge-check.sh` over every module, and that check's own tests |
| A block shows only state its contributor keeps | the catalogue check; the controller's `TestAModuleOffersItsOwnStateOnly` |
| The holder is granted the read of what the modules on its machine offer, and nothing more | `TestAStateBlockIsRenderedForThisMachineAndTheHolderIsGrantedToReadIt`; `TestABarIsGrantedTheStateItsMachinesModulesOfferIt` against a real store |
| Only a fresh reading that measured something is put, keyed by the machine | the power module's `publish_test.go` |
| The bar keeps a value's text, removes a deleted one, retries a refused watch, and hides an old value | the i3status-rust module's `values_test.go` |
## References
- [ADR 0255](0255-the-bar-takes-blocks-as-data-from-the-module-that-knows-the-hardware-placed-where-the-machine-has-it.md) §5 and §9, which this extends
- [ADR 0201](0201-a-module-keeps-its-current-state-in-key-value-buckets-it-declares-and-reaches-through-the-runtime.md), state on the bus
- [ADR 0210](0210-a-tools-configuration-is-its-seat-holders-and-every-other-module-extends-it-through-the-seat.md)
- [To-be 42](../03-DESIGN/01-to-be/42-the-machines-modules-in-order.md)
@@ -20,6 +20,7 @@ decisions:
- 02-DECISIONS/0211-a-machines-power-is-a-node-seat-its-moments-take-contributions-and-its-states-are-events.md
- 02-DECISIONS/0210-a-tools-configuration-is-its-seat-holders-and-every-other-module-extends-it-through-the-seat.md
- 02-DECISIONS/0255-the-bar-takes-blocks-as-data-from-the-module-that-knows-the-hardware-placed-where-the-machine-has-it.md
- 02-DECISIONS/0260-a-bar-block-shows-a-value-its-module-keeps-on-the-bus-and-no-module-reads-anothers-files.md
---
# 42. The machines' modules, in order
@@ -131,6 +132,13 @@ both places of both bars once. The bar writes no block that depends on the hardw
be its block.
- The bar's memory block shows what programs hold, the figure `memory-pressure` judges by.
**Changed 2026-10-08** ([ADR 0260](../../02-DECISIONS/0260-a-bar-block-shows-a-value-its-module-keeps-on-the-bus-and-no-module-reads-anothers-files.md)): modules talk over the bus. The power draw is
the power module's state `power.draw`, one value per machine, which its bundle puts from its own root
sampler. The block `shows: state`; the bar's bundle watches it under a read the controller grants the
bar for what the modules on its machine offer, and the bar names no module. The battery block is the
bar's own, reading the kernel. The catalogue's `checks/paths` refuses a module that names another
module's path under `/run` or `/var/lib`.
The node-engine is delivered first, so the machines report the two capabilities. The controller comes
next, because it parses `data`, `if-capability` and `renders`. The catalogue comes last. Left open: the
draw at the wall (a metered plug or a UPS), a GPU-load block from a generic module, and the headsets.