Merge pull request 'ADR 0260: a bar block shows a value its module keeps on the bus, and no module reads another's files' (#212) from feat/the-bar-takes-blocks into main
This commit was merged in pull request #212.
This commit is contained in:
+4
@@ -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.
|
||||
|
||||
|
||||
+139
@@ -0,0 +1,139 @@
|
||||
---
|
||||
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 that
|
||||
another module's manifest places under `/run`, `/var/lib`, `/usr/local/lib` or `/etc`. Within one
|
||||
module, parts may pass a file: its root process and its bundle are one module.
|
||||
|
||||
Two exceptions are written down beside the check:
|
||||
- a short list of paths every program on a machine reads as the machine's own, such as `/etc/hosts`;
|
||||
- the cross-module reads that stood on the day the rule took `/etc` in. These may only go: there are
|
||||
five, all the laptop model's module reading the power module's and the hotkey daemon's files.
|
||||
|
||||
A path a container sees inside itself is the container's own. The check reads text, so it cannot see
|
||||
a path built in code from pieces, read from a setting, or reached through a shell the module runs.
|
||||
|
||||
**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`, lower-case letters, digits and hyphens, 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 of one key, without naming the contributor.** The controller
|
||||
derives the holder's reads per machine: each state that the modules on the holder's machine offer in
|
||||
pieces of a kind its seat receives as data, and of each only the key the piece shows. The bar's manifest
|
||||
names no power module, and a bar on a machine without the power module reads nothing of it.
|
||||
- On the bus the grant is that one key: a direct get of its subject, and a consumer filtered to it.
|
||||
A client watching one key creates its consumer by the create-with-filter form, which the server
|
||||
holds to the filter it names.
|
||||
- Binding to the bucket, deleting a consumer and flow control stay per bucket. None of them reads or
|
||||
writes a value.
|
||||
- **State may be per machine.** A state declared `per-machine` is written and read by each machine's
|
||||
instance at the key named for its machine, and the bus grants that key and no other. The power
|
||||
module's `draw` is per machine, so a machine's power module can write only its own draw.
|
||||
- One account per machine carries every module there, so the bus enforces the union of their grants.
|
||||
For the power draw, that union is still the machine's one key.
|
||||
- The membership names the key, so the runtime can refuse first. Until it does, the bus refuses.
|
||||
|
||||
**4. The power draw is the power module's state `draw`, per 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;
|
||||
- it dates each value by its own `at`, not by when it arrived;
|
||||
- the block runs the bar module's own command, which prints the text, or nothing when the value was
|
||||
measured more than 30 seconds ago, so a value frozen at its source hides;
|
||||
- a watch that says nothing for twice the value's lifetime is read again, and the bundle says so in its
|
||||
log;
|
||||
- a refused watch is logged and tried again;
|
||||
- a value no bar shows any more is dropped. The SDK has no way to end a watch, so a dropped watch
|
||||
lasts until the bundle restarts, and what it hands over is ignored.
|
||||
|
||||
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`, `/var/lib`, `/usr/local/lib` or `/etc` that another module's manifest places; the known exceptions only shrink | the catalogue's `checks/paths` with its `shared` and `known` lists, 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 one key each piece on its machine shows, and nothing more | `TestAStateBlockIsRenderedForThisMachineAndTheHolderIsGrantedToReadIt`; `TestABarIsGrantedTheStateItsMachinesModulesOfferIt` against a real store |
|
||||
| A keyed read and per-machine state reach one key on the bus, also as the machine's union | the controller's `TestPerMachineStateAndAKeyedReadReachOneKey`, `TestAMembershipNamesTheOneKeyOfKeyedState` |
|
||||
| Only a fresh reading that measured something is put, keyed by the machine | the power module's `publish_test.go` |
|
||||
| The bar dates a value by its reading, says a refused or silent watch, rereads a silent one, drops a removed block, and hides a value measured more than 30 s ago | 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.
|
||||
|
||||
Reference in New Issue
Block a user