100 lines
7.1 KiB
Markdown
100 lines
7.1 KiB
Markdown
---
|
|
topic: the mesh
|
|
status: accepted
|
|
date: 2026-10-01
|
|
deciders: jochen
|
|
reconstructed: false
|
|
extends: 02-DECISIONS/0121-a-system-seat-is-named-for-its-scope-and-modules-define-their-own.md
|
|
---
|
|
|
|
# 157. A build says what it does on the bus, as it happens
|
|
|
|
## Context
|
|
|
|
[ADR 0121](0121-a-system-seat-is-named-for-its-scope-and-modules-define-their-own.md) made a build work submitted to a role: the
|
|
build-machine seat accepts a build and emits its outcome, one publish that reaches whoever asked, the
|
|
controller that records it and the catalogue that places it. Everything **between** the request and
|
|
the outcome — which command is running, how long it has taken, where it hung, the compiler's error,
|
|
the clone's refusal — lived in one container's standard error on one machine.
|
|
|
|
The night of 2026-09-30 showed the cost three times over. A build that failed showed a person one
|
|
line, the first of its failure, in the controller's `builds`; the rest was read with `docker logs` over
|
|
ssh, which the mesh's own rule forbids. A build that ran for minutes could not be told from one that
|
|
had hung. And the builder has no tools and emits nothing but the outcome, so the console
|
|
([ADR 0152](0152-the-operators-surface-is-a-module-the-console.md)) had nothing to show while a
|
|
build ran, and no viewer could be built on top of it. The operator's ask was plain: the builder is to
|
|
be fully transparent, with its log on the bus, so that a log viewer can be built on the bus later.
|
|
|
|
## Considered Options
|
|
|
|
1. **Keep the log in the outcome.** The result carries the whole log when the build ends. Nothing new
|
|
on the bus; nothing while the build runs; a viewer sees a build only once it is over, which is
|
|
exactly when the log matters least.
|
|
2. **A log store.** The builder writes its log to a file or a table and a tool reads it. A second
|
|
place to keep something the bus already carries, with its own retention, access and failure modes,
|
|
and no live reading without inventing a subscription over it.
|
|
3. **The log is the role's own events.** Two more events on the build-machine seat beside `built`:
|
|
`started` when work is taken, and `log.<build id>` for every line, published as the build runs.
|
|
The events stream already retains every role's events for a week, so a reader follows a build
|
|
live by subscribing its subject, or reads it back afterwards from the stream, and a viewer is a
|
|
subscriber and nothing more.
|
|
|
|
## Decision
|
|
|
|
**Option 3.** A build machine says everything it does on the bus, as the role it holds, under the
|
|
build's id, and the mesh keeps no other copy.
|
|
|
|
- The build-machine seat's protocol gains `started` and `log.*`. A holder may therefore publish
|
|
`mesh.seat.mesh-build-machine.event.started` and `…event.log.<id>`, and no other subject, by the
|
|
same derivation every seat's grants follow ([ADR 0132](0132-a-seat-carries-the-tools-its-holder-must-serve.md)).
|
|
The event's tail token is the build's id, so one build is one subject: a reader filters by subject
|
|
alone, on the server, and a week of other builds does not travel to show one.
|
|
- **Every line goes two ways**: to the machine's own standard error as before, and onto the bus. That
|
|
includes every command the builder runs, its duration and its failure, and on failure the command's
|
|
own output line by line — the compiler's words, the clone's refusal. A build machine with nobody
|
|
listening still prints; a listener reads the same lines.
|
|
- A line is a core publish, unawaited. The stream that holds the role's events captures it on its way
|
|
through, and a build does not slow to the pace of an acknowledgement per line. Each line carries a
|
|
sequence number from one, so a reader who joined late, or reads two copies, sees order and gaps.
|
|
`started` and `built` are published into the stream and awaited, because they are the two facts a
|
|
later reader must never find missing.
|
|
- **The mesh reads it back from the stream**, never from a record of its own: `builds --log <id>`, and
|
|
the same verb on the controller's seat ([ADR 0154](0154-the-meshs-own-verbs-are-the-controller-seats-tools.md)),
|
|
reads one build's subject with a consumer that is gone when the reading is done. `builds` lists
|
|
each build's id beside it, and `build` says the id it asked with, so a person can follow.
|
|
- Nothing is declared by the builder module for this. The protocol is the seat's, seeded additively
|
|
into the store ([ADR 0132](0132-a-seat-carries-the-tools-its-holder-must-serve.md)), and the
|
|
holder's grant follows on the next composition of the broker node.
|
|
|
|
## Consequences
|
|
|
|
- A build is watchable while it runs, from anywhere on the mesh, with no access to the build machine.
|
|
The console's gap of 2026-10-01 — no live progress, no per-merge view — closes on the progress half;
|
|
the per-merge view is a reader over these subjects and the outcome, and is not built here.
|
|
- A log viewer on the bus is now a plain subscriber: live on `mesh.seat.mesh-build-machine.event.>`,
|
|
historical from the events stream filtered by a build's subject. NATS carries and retains; it does
|
|
not view. The `nats` command-line client can tail or replay a subject today; a viewer of our own is
|
|
later work and needs nothing more from the builder.
|
|
- The events stream grows by a build's log per build, for a week. A build is a few hundred lines; the
|
|
stream's limits are the bound, as for every other event, and a stream that fills drops the oldest.
|
|
- A line the bus did not take is lost, deliberately, and visible as a gap in the sequence. The outcome
|
|
is not affected: a build's result never depended on its narration.
|
|
|
|
## How this is checked
|
|
|
|
| Rule | Checked by |
|
|
|---|---|
|
|
| The seat's holder may publish `started` and `log.<id>` and nothing wider | `TestTheBuildMachineMaySayWhatItDoesUnderTheBuildsId` (broker) |
|
|
| A build's lines reach a reader of its subject in order, and the stream holds them afterwards | `TestNatsABuildIsTakenAndItsOutcomeReachesEverybody` against a real server (link) |
|
|
| The seat verb `builds` with a build's id reads that build's log | `TestBuildsWithAnIdReadsThatBuildsLog` |
|
|
| Every command the builder runs is said, with its output on failure | `Command` speaks through the hook every build sets; the builder's tests still see the lines on standard error when nothing listens |
|
|
| Live: a build triggered after the roll-out is readable line by line through the console | done by hand after the merge of mesh-controller PR — see the design's note |
|
|
|
|
## References
|
|
|
|
- [ADR 0121](0121-a-system-seat-is-named-for-its-scope-and-modules-define-their-own.md) — extended: the role now narrates as well as answers
|
|
- [ADR 0132](0132-a-seat-carries-the-tools-its-holder-must-serve.md), [ADR 0154](0154-the-meshs-own-verbs-are-the-controller-seats-tools.md) — the protocol and the verb
|
|
- [ADR 0152](0152-the-operators-surface-is-a-module-the-console.md) — the console this feeds
|
|
- [Design 25 — The bus on NATS](../03-DESIGN/01-to-be/25-the-bus-on-nats.md) §3, [Design 18 — Building a module](../03-DESIGN/01-to-be/18-building-a-module.md)
|
|
- [Issue 176](../04-ISSUES/176-the-consoles-build-tool-neither-waits-nor-registers/00-report.md) — the tool that starts a build and hears nothing; this gives it something to hear
|