diff --git a/02-DECISIONS/0255-the-bar-takes-blocks-as-data-from-the-module-that-knows-the-hardware-placed-where-the-machine-has-it.md b/02-DECISIONS/0255-the-bar-takes-blocks-as-data-from-the-module-that-knows-the-hardware-placed-where-the-machine-has-it.md index b6c69817..32c3ee57 100644 --- a/02-DECISIONS/0255-the-bar-takes-blocks-as-data-from-the-module-that-knows-the-hardware-placed-where-the-machine-has-it.md +++ b/02-DECISIONS/0255-the-bar-takes-blocks-as-data-from-the-module-that-knows-the-hardware-placed-where-the-machine-has-it.md @@ -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. diff --git a/02-DECISIONS/0260-a-bar-block-shows-a-value-its-module-keeps-on-the-bus-and-no-module-reads-anothers-files.md b/02-DECISIONS/0260-a-bar-block-shows-a-value-its-module-keeps-on-the-bus-and-no-module-reads-anothers-files.md new file mode 100644 index 00000000..aaf0b0cb --- /dev/null +++ b/02-DECISIONS/0260-a-bar-block-shows-a-value-its-module-keeps-on-the-bus-and-no-module-reads-anothers-files.md @@ -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 + `.`. 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 `.`; +- 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) diff --git a/03-DESIGN/01-to-be/42-the-machines-modules-in-order.md b/03-DESIGN/01-to-be/42-the-machines-modules-in-order.md index 8fae6b40..46d5cb29 100644 --- a/03-DESIGN/01-to-be/42-the-machines-modules-in-order.md +++ b/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.