Merge pull request 'ADR 0255: the bar takes blocks as data from the module that knows the hardware' (#205) from feat/the-bar-takes-blocks into main
This commit was merged in pull request #205.
This commit is contained in:
@@ -93,6 +93,10 @@ reports, `graphical-session`, still gates the display server itself.
|
||||
> included, is a dependency on the seat that receives it. What a contribution contains still follows
|
||||
> the tool's grain.
|
||||
|
||||
> **Extended — 2026-10-08, by [ADR 0255](0255-the-bar-takes-blocks-as-data-from-the-module-that-knows-the-hardware-placed-where-the-machine-has-it.md).** A seat may receive a kind as data in a shape the seat
|
||||
> defines, rendered by its holder's template: `node-bar` receives blocks that way. A contribution may
|
||||
> also be placed only where the machine reports a capability.
|
||||
|
||||
**5. The display server's module writes the session's start.** It writes a block at the start of
|
||||
`~/.xinitrc`, in this order:
|
||||
|
||||
|
||||
+207
@@ -0,0 +1,207 @@
|
||||
---
|
||||
topic: what runs on it
|
||||
status: accepted
|
||||
date: 2026-10-08
|
||||
deciders: jochen
|
||||
reconstructed: false
|
||||
extends: 02-DECISIONS/0208-the-graphical-session-is-one-module-per-piece-on-the-meshs-seats.md
|
||||
---
|
||||
|
||||
# 255. The bar takes blocks as data from the module that knows the hardware, placed where the machine has it
|
||||
|
||||
## Context
|
||||
|
||||
The bar's module claims `node-bar` ([ADR 0208](0208-the-graphical-session-is-one-module-per-piece-on-the-meshs-seats.md)) and
|
||||
writes both bars' files whole. When it was adopted from the workstations on 2026-10-04, three kinds of
|
||||
block were left out because they were not the bar's to know:
|
||||
|
||||
- the battery, which only the laptop has;
|
||||
- the GPU's load, an AMD block on the laptop, commented out for NVIDIA on the workstation;
|
||||
- three Bluetooth headsets by hardware address.
|
||||
|
||||
The bar's own documentation said that a block which follows a machine's hardware belongs to the module
|
||||
that knows that hardware. It also said such a module had no way in. ADR 0208 §4 lets a module
|
||||
contribute to a holder's file "in the tool's own grain", and
|
||||
[ADR 0212](0212-a-seat-says-what-it-receives-and-the-machines-hotkeys-are-a-seat.md) made that one
|
||||
general form: a seat, a kind the seat receives, and text in the tool's own grammar, which the controller
|
||||
places and never reads. `node-bar` received nothing. Since then the laptop's bar has shown no battery.
|
||||
|
||||
Measured on the laptop on 2026-10-08, the bar's memory block read 96 %. It counted the cache the kernel
|
||||
gives back when asked as used. The `memory-pressure` module, which judges the same machine, read 73 %.
|
||||
|
||||
Three facts shaped the decision:
|
||||
|
||||
- **The text form would tie every contributor to one bar.** A module giving the bar a battery under
|
||||
ADR 0212 would write i3status-rust's own grammar. A second bar module, waybar or polybar, could not
|
||||
read it, and the seat exists so that its holder can be replaced.
|
||||
- **The mesh already separates data from format once.** A module's `facts` give a path and a template.
|
||||
The controller renders the mesh's roster through the template. The data is the mesh's and the format
|
||||
is the module's.
|
||||
- **What a machine has is already reported.** The node-engine reports detected capabilities, each with
|
||||
what it saw and how. The controller uses them to refuse a module a machine cannot run. A battery is
|
||||
not among them yet.
|
||||
|
||||
## Considered Options
|
||||
|
||||
1. **The seat receives blocks as data, and its holder renders them.** A contribution says what the block
|
||||
shows in terms no bar owns. The holder turns it into its own grammar. Chosen by the operator on
|
||||
2026-10-08: "option 1 is exactly how it should work, other modules can 'provide' block information in
|
||||
some form or another (use a generic boundary layer)".
|
||||
2. **Blocks that hide themselves where they do not apply.** i3status-rust's common `if_command` option
|
||||
(a test for the battery's directory, say) hides a block at run time on a machine without the
|
||||
hardware. Rejected: the knowledge stays in the bar, every bar asks the same question again at run
|
||||
time, and another bar module would have to carry the same tests in its own grammar.
|
||||
3. **A text contribution in the bar's grammar, under ADR 0212 as it stands.** Rejected for the reason
|
||||
above: the contributor would write one bar's grammar, and the seat could not change hands.
|
||||
4. **A provision the bar requires.** Rejected: a provision is a service one module serves another
|
||||
([ADR 0040](0040-what-a-module-is.md)) and is resolved to one provider. A bar takes pieces from any
|
||||
number of modules on its own machine, and that is what a seat's contributions already are.
|
||||
|
||||
On the source of the battery, the operator ruled on the same day that generic information must not come
|
||||
from a vendor's module. A battery is any laptop's. The laptop model's module was the first candidate
|
||||
and is not the source.
|
||||
|
||||
## Decision
|
||||
|
||||
**1. A seat may receive a kind as data.** The seat defines the kind's shape: its fields, their values,
|
||||
and which fields place a piece. A contribution of that kind gives `data` in that shape instead of
|
||||
`content`. The holder's claim gives a template for the kind in `renders`: a template over one piece,
|
||||
written in its tool's grammar. The controller renders each piece through it and places the result.
|
||||
Each piece is preceded by a comment naming its module, as in ADR 0212 §3. The template is given one
|
||||
function, `quote`, which writes a value as a JSON string with DEL escaped too: a valid basic string in
|
||||
TOML and in most tools' grammars. A kind given as text stays as ADR 0212 decided.
|
||||
|
||||
**2. The holder places a data kind by its placing fields.** The placeholder is
|
||||
`${contribution:<seat>:<kind>:<field>=<value>,…}`. Within one placeholder, pieces go in the order the
|
||||
shape names, then in module order. Across all of its files, the holder places every combination of
|
||||
the placing fields exactly once. Its template must render every example the seat defines. If a
|
||||
combination is missing, a contribution is lost. If it is placed twice, the contribution is written
|
||||
twice. Both are refused at registration. A holder that places none of the kind is not refused. It was
|
||||
written before the kind existed, and its contributions wait until it places them.
|
||||
|
||||
A piece the holder's template renders to nothing, or fails on, is left out of that machine's
|
||||
declaration and named, with its module and why. This happens to a `shows` the template does not know
|
||||
yet. The rest of the machine's declaration is sent. Push and plan say which pieces were left out, and
|
||||
the merge gate refuses a change that leaves out a piece that was placed before.
|
||||
|
||||
**3. A contribution of an offered kind may name a capability the machine must report:
|
||||
`if-capability`.** The controller composes it only for a machine whose node-engine reported that
|
||||
capability. Where nothing is known, there is nothing. The machine's facts decide at composition. The
|
||||
tool does not decide at run time.
|
||||
- The name must be one the node-engine detects. A name nothing detects would leave the piece out on
|
||||
every machine without a word, so it is refused at registration.
|
||||
- Only a kind the seat offers (§4) may say it. A kind a holder depends on, such as what a backup keeps
|
||||
or a key's trigger, is never left out for want of something, so `if-capability` is refused there.
|
||||
|
||||
**4. A kind may be offered.** A contribution of an offered kind does not depend on the seat
|
||||
(an exception to ADR 0212 §4). Where nobody holds the seat, nothing is placed, and the contributor
|
||||
runs there all the same.
|
||||
|
||||
**5. `node-bar` receives `block`, offered, as data.** A block has these fields:
|
||||
- `bar`: `bottom` or `top`;
|
||||
- `place`: `resources`, beside the processor, memory and disks, or `status`, beside the sound and
|
||||
before the clock;
|
||||
- `order`: a whole number from 0 to 99, 50 when absent;
|
||||
- `shows`: `battery` (option `device`) or `command` (options `command` and `interval`, one line the
|
||||
command prints, every interval seconds);
|
||||
- `options`: the options of what it shows.
|
||||
|
||||
Nothing in a block belongs to one bar. A new kind of block is added to the seat's shape, and every
|
||||
holder's template must render it before the shape is accepted.
|
||||
|
||||
**6. Generic hardware is shown by the module that holds the generic seat, conditioned on the machine's
|
||||
facts.** A vendor's module contributes only what is that vendor's alone. The power module holds
|
||||
`node-power` on every machine, so it contributes:
|
||||
- the battery block, `if-capability: battery`;
|
||||
- the power-draw block, `if-capability: power-meter`.
|
||||
|
||||
The laptop model's module contributes no block. Something only its vendor's daemon knows would be its
|
||||
block, such as the charge limit asusd holds. None is contributed yet.
|
||||
|
||||
**7. The node-engine reports two more capabilities, read from the kernel:**
|
||||
- `battery`: a battery that powers the machine. A device's battery, such as a mouse's or a headset's,
|
||||
is not one, and neither is an empty bay.
|
||||
- `power-meter`: something here measures the power drawn, the same meters the power module's sampler
|
||||
reads. That is a system battery, a processor package's powercap energy counter (RAPL, on Intel and
|
||||
AMD), or a discrete graphics device that reports its own power. Each package is counted once by its
|
||||
name, and integrated graphics, which report the package again, are not counted.
|
||||
|
||||
**8. The power drawn is measured honestly, and the block says what was measured.**
|
||||
- **On battery:** the battery's discharge rate. That is the whole machine, and the block says
|
||||
`machine`.
|
||||
- **On mains, or on a machine without a battery:** the processor package's power, as a rate of
|
||||
change of its energy counter. A discrete graphics device's own power is added where the device
|
||||
reports it. The block says `CPU` or `CPU+GPU`, never the machine.
|
||||
- An APU's integrated graphics report the whole package, so they are not counted a second time.
|
||||
- A package Intel shows twice (through its MSRs and through MMIO) is read once.
|
||||
- A sleeping GPU, AMD or NVIDIA, is neither read nor woken to be asked.
|
||||
- A counter that gives more than 1000 W was reset, after a resume for example. That sample gives no
|
||||
reading rather than a number that is not power.
|
||||
- **The machine at the wall** can only be measured by a metered plug or a UPS. It is not measured.
|
||||
|
||||
**9. The energy counter stays root's.** Read often, it leaks what the processor is doing (the
|
||||
PLATYPUS attack, CVE-2020-8694), which is why the kernel makes it root's. No permission is loosened.
|
||||
- The power module runs a sampler as root. It reads the counter every five seconds and publishes
|
||||
only the averaged rate, as one line anyone may read.
|
||||
- The bar's command prints that line. It says the power is not measured when the line is missing
|
||||
or older than thirty seconds.
|
||||
- **The sampler runs wherever the power module is assigned, servers included.** A server has the
|
||||
energy counter too, so conditioning the sampler on `power-meter` would not keep it off one. The mesh
|
||||
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.
|
||||
|
||||
**10. The memory block shows what programs hold,** not counting the cache. That is the figure
|
||||
`memory-pressure` judges the same machine by.
|
||||
|
||||
## Consequences
|
||||
|
||||
- The laptop's bar gains the battery and the power draw. A workstation's bar gains the power draw, and its
|
||||
processor's part is labelled as such. A server has no bar, and nothing is placed there.
|
||||
- Replacing the bar's module costs one template. No contributor changes.
|
||||
- `node-bar` is the first seat to receive data. `node-hotkeys`, `node-display-session` and
|
||||
`node-backup` keep text.
|
||||
- **Version skew:** an older controller refuses a manifest with `data`, `if-capability` or `renders`.
|
||||
The controller is delivered before the catalogue, as a delivery group's order rules already require
|
||||
([ADR 0239](0239-a-delivery-is-owned-by-the-mesh-delivery-module-and-runs-from-commit-to-delivered.md) §4).
|
||||
The node-engine is delivered first of all. Until a machine's node-engine reports `battery` and
|
||||
`power-meter`, that machine's bar shows neither block, and nothing fails.
|
||||
- **What got harder:**
|
||||
- The controller now runs a holder's template, and a template can be wrong. Its examples are rendered
|
||||
at registration, and a value it prints but does not have is refused, not written as `<no value>`.
|
||||
- The kind's shape is code in the controller's seat table, as the seats' verbs are. A new block kind
|
||||
is a change to the controller and to every holder's template.
|
||||
- **Left open:**
|
||||
- the machine's draw at the wall, from a metered plug or a UPS;
|
||||
- a GPU-load block, which belongs to a generic module that does not exist;
|
||||
- a vendor-only block, such as the laptop's charge limit;
|
||||
- the sampler on a machine without a bar, until a resource can be conditioned on a seat being held;
|
||||
- the headsets, which are one person's devices.
|
||||
|
||||
## How it is checked
|
||||
|
||||
| Rule | Checked by |
|
||||
|---|---|
|
||||
| A data contribution fits its seat's shape (fields, values, required options, order 0–99), and text and data are not mixed | the catalogue check; the controller's `TestABlockOutsideTheShapeIsRefused` |
|
||||
| A holder places every combination of the placing fields once, with a template that renders every example | the catalogue check; `TestAHolderPlacesEveryCombinationOnceWithATemplateThatRendersEveryExample` |
|
||||
| Pieces are rendered by the holder's template, in place and order, each named by module | `TestABlockIsRenderedByTheHolderInItsPlaceAndOrderNamedByModule` |
|
||||
| `if-capability` places a contribution only where the machine reported the capability | `TestAContributionIfACapabilityIsPlacedOnlyWhereTheMachineReportsIt`, `TestResolveCarriesTheMachinesCapabilities` |
|
||||
| `if-capability` names a capability the node-engine detects, on an offered kind only | the catalogue check; `TestIfACapabilityNamesOneTheNodeEngineDetectsOnAnOfferedKindOnly` |
|
||||
| A piece the holder cannot render is left out and named, and the machine gets the rest | `TestAPieceTheHolderCannotRenderIsLeftOutAndNamedNotTheMachine`; the merge gate refuses a new one |
|
||||
| The catalogue's bar renders every example of the shape | `TestTheCataloguesBarRendersEveryExampleOfTheShape` |
|
||||
| `quote` gives a TOML basic string of the same text | `TestQuoteIsATomlBasicStringOfTheSameText` |
|
||||
| An offered kind makes no dependency on its seat | `TestAnOfferedBlockMakesNoDependencyOnTheBar` |
|
||||
| `battery` and `power-meter` are read from the kernel, and a device's battery is not the machine's | the node-engine's `power_test.go` |
|
||||
| The power draw says `machine` only on battery, counts each package once, never counts an APU's graphics twice, never reads a sleeping GPU, and gives no reading for a reset counter | the power module's `draw_test.go` |
|
||||
| The sampler publishes once per interval and stops when told; the bar's script shows only a line under thirty seconds old | `TestTheLoopPublishesEveryIntervalAndStopsWhenCancelled`, `TestTheBarsScriptShowsOnlyAFreshLine` |
|
||||
| Only a rate leaves root, readable by anyone | the power module's `TestOnlyTheRateIsPublishedReadableByAnyone` |
|
||||
| The bar's template renders a battery and a quoted command, each bar places both places once, and the memory block uses what programs hold | the i3status-rust module's `blocks_test.go` |
|
||||
| On the machines | the merge gate's composition of every machine, and after rollout the bar's `i3status_rust_blocks` tool on the laptop and a workstation |
|
||||
|
||||
## References
|
||||
|
||||
- [ADR 0208](0208-the-graphical-session-is-one-module-per-piece-on-the-meshs-seats.md) §4, which this extends
|
||||
- [ADR 0210](0210-a-tools-configuration-is-its-seat-holders-and-every-other-module-extends-it-through-the-seat.md),
|
||||
[ADR 0212](0212-a-seat-says-what-it-receives-and-the-machines-hotkeys-are-a-seat.md) §1, §3 and §4, whose
|
||||
general form this widens
|
||||
- [ADR 0211](0211-a-machines-power-is-a-node-seat-its-moments-take-contributions-and-its-states-are-events.md), the power seat
|
||||
- [To-be 42](../03-DESIGN/01-to-be/42-the-machines-modules-in-order.md)
|
||||
@@ -2,7 +2,7 @@
|
||||
layer: to-be
|
||||
status: in-progress
|
||||
code: [mesh-catalog, mesh-controller, mesh-host]
|
||||
updated: 2026-10-04
|
||||
updated: 2026-10-08
|
||||
decisions:
|
||||
- 02-DECISIONS/0215-the-machines-message-bus-is-a-node-seat-and-is-never-restarted-live.md
|
||||
- 02-DECISIONS/0173-the-operators-machine-is-the-meshs-and-a-module-is-what-it-declares.md
|
||||
@@ -19,6 +19,7 @@ decisions:
|
||||
- 02-DECISIONS/0212-a-seat-says-what-it-receives-and-the-machines-hotkeys-are-a-seat.md
|
||||
- 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
|
||||
---
|
||||
|
||||
# 42. The machines' modules, in order
|
||||
@@ -118,6 +119,22 @@ laptop model's vendor keys become its contribution. The window-manager fragments
|
||||
clipboard, the wallpaper, the bar and the laptop model become `config` contributions to
|
||||
`node-display-session`.
|
||||
|
||||
**Added 2026-10-08.** `node-bar` receives blocks as data ([ADR 0255](../../02-DECISIONS/0255-the-bar-takes-blocks-as-data-from-the-module-that-knows-the-hardware-placed-where-the-machine-has-it.md)). A block
|
||||
says which bar, which place (`resources` or `status`), an order, and what it shows (`battery`, or one
|
||||
line a `command` prints). `i3status-rust` renders it with the template its claim gives and places
|
||||
both places of both bars once. The bar writes no block that depends on the hardware:
|
||||
- `power` contributes the battery where the node-engine reports `battery`, and the power draw where it
|
||||
reports `power-meter`. On battery the draw is the machine's discharge; otherwise it is the processor
|
||||
package's power from its energy counter, plus a discrete GPU's own, and says so. A root sampler
|
||||
publishes only the rate, so the counter stays root's.
|
||||
- The laptop model's module contributes no block. A vendor-only figure, such as its charge limit, would
|
||||
be its block.
|
||||
- The bar's memory block shows what programs hold, the figure `memory-pressure` judges by.
|
||||
|
||||
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.
|
||||
|
||||
## Phase 3 — one machine model
|
||||
|
||||
The laptop's hardware module (vendor daemon, GPU mode, charge limit, logind, brightness and vendor keys)
|
||||
|
||||
Reference in New Issue
Block a user