From f6d4d6a8c46be82b9743fe5d4442d3321191163d Mon Sep 17 00:00:00 2001 From: Nils Lehnen <30603423+iderex@users.noreply.github.com> Date: Tue, 11 Aug 2026 23:01:02 +0200 Subject: [PATCH] Write the operator guide (#42) The page somebody who has never seen this board reads before running anything from it. It walks getting the runner, a first run with its exact output, a run over a tree with no experiments, the listing, and what a refusal looks like, and it carries the three questions somebody decides in half a minute on: that the runner changes nothing, sends nothing anywhere, and needs no privileges. Each of the three is written as a limit with the test or the record behind it rather than as an assurance. The exit codes are explained by pointing at record 0011 rather than by restating it, and what the page adds is the half a record cannot show: which invocation produces each code, with the command and the output. It also says that the runner returns three of the four the record fixes and where the fourth one's producer is. Two absences are stated rather than papered over. There is nothing to download, so nothing here walks a download or a checksum, and whether anything is ever published is not decided. There is no licence file, so the page names its absence where a reader would otherwise look for permission to reuse what they find. The compatibility position lands here because it has nowhere else to live yet. It points at record 0013 for what happens to records already on the default branch when the format changes, and says that a change is announced by the record that decides it, there being no changelog in the tree. Signed-off-by: Nils Lehnen <30603423+iderex@users.noreply.github.com> --- docs/operator-guide.md | 209 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 209 insertions(+) create mode 100644 docs/operator-guide.md diff --git a/docs/operator-guide.md b/docs/operator-guide.md new file mode 100644 index 0000000..dc5acd9 --- /dev/null +++ b/docs/operator-guide.md @@ -0,0 +1,209 @@ +# The operator guide + +This page is for somebody who has never seen this board and wants to check it +for themselves. It walks getting the runner, running it once, and reading what +comes back, including the runs that end in a refusal. + +## What you get, before you decide to run anything + +`lab` reads a checkout of this repository and reports whether its records are +in order and what it examined. Three things somebody deciding in half a minute +whether to run an unfamiliar program wants to know, and each of the three is a +limit rather than a promise. + +It changes nothing in the tree it reads. Every verb it has reads and none of +them writes, and the walk is held to that by a test rather than by this +sentence: + + go test ./internal/check -run TestWalkWritesNothing -count=1 + +That test fingerprints every fixture tree by path and by content, walks all of +them, and fingerprints again, so a file created, changed or removed anywhere +under them is a difference it reports. What it covers is the walk over those +trees. It is not a statement about a tree it has never been pointed at. + +It sends nothing anywhere. The claim, the command that tests it, and the bound +on what that test proves are in [docs/privacy.md](privacy.md). + +It needs no privileges and no graphical session. Both halves bind from the +first test on this board rather than from a later mechanism, which is +[docs/decisions/0007-headless-by-birth.md](decisions/0007-headless-by-birth.md). + +## Getting it + +There is nothing to download. This board publishes no binary, no checksum file +and no release, so nothing on this page tells you how to verify a download, and +whether anything is ever published is not decided. The route that exists is a +checkout and a Go toolchain, and the toolchain version comes from `go.mod`: + + git clone https://github.com/Flowfin/lab + cd lab + go build -o lab ./cmd/lab + +Building the tool from the repository it is about is a weaker position than +downloading one somebody else built, and it is the only position available +today. The source of the checks is in the same checkout, which is the part that +makes it worth anything: what each rule refuses is readable next to the rule. + +## The first run + + ./lab check . + +Against a fresh clone of this repository at commit `bbfab50`, that printed: + + examined . + 1 experiment directory walked, 1 record read + 16 decision records read + the time this run read is 2026-08-11T20:42:12Z + 0 refused + +Four things in those lines. What was examined, which is the path you gave it. +What the walk found and what it managed to read, counted apart from each other, +because a directory walked with no record read is the shape of an experiment +that states no question. What the run read the clock as, so a verdict about a +date can be placed against the moment it was made in rather than the moment you +are reading in. And what was refused, printed as a number even when the number +is zero. + +The counts move as the board grows, so a later run over a later tree prints +different numbers in the same shape. The commit is named above because that is +what the numbers are reproducible against. + +## A run over a tree with no experiments + +This is what a run against a directory holding nothing produces, and it is a +result rather than a broken run: + + examined . + no experiments directory in this tree + 0 experiment directories walked, 0 records read + no docs/decisions directory in this tree + 0 decision records read + the time this run read is 2026-08-11T20:43:16Z + 0 refused + +The two lines saying a directory is not there are what make that readable. A +run that examined nothing and a run that examined everything and found nothing +otherwise print the same zero, and telling them apart is the difference between +a clean board and a run that never happened. + +## The listing + +`lab list` reports the experiments instead of judging them. Over the same fresh +clone: + + ./lab list . + + examined . + 1 experiment + the time this run read is 2026-08-11T20:42:15Z + slug state question written waiting needs + reading-a-tree-of-records answered 2026-08-11 - none + +Oldest unanswered first, so whatever has been asking longest is at the top. +Nothing here fails because an experiment is old, and the listing exists to make +the choice between answering one and abandoning it a visible one. + +## What a refusal looks like + +A refusal names the thing to open, says what is wrong in a sentence, and ends +with the rule that refused it. This run was made over a directory holding one +experiment directory with no record in it: + + examined . + 1 experiment directory walked, 0 records read + no docs/decisions directory in this tree + 0 decision records read + the time this run read is 2026-08-11T20:43:40Z + 1 refused + experiments\a-question-with-no-record: there is no EXPERIMENT.md in it, so it states no question (experiment-has-no-record) + +The separator in that path is the one the host uses, and that run was made on +Windows. + +The name in brackets is the rule. It is the string to search for in the +checkout when the sentence is not enough, and it takes you to the place where +what the rule refuses and what it deliberately does not judge are written +together. The report still says what was examined, so a run that refused +something also tells you how much of the tree it got through. + +## The exit codes + +What each code means is +[docs/decisions/0011-the-exit-codes.md](decisions/0011-the-exit-codes.md), and +it is not restated here. A second copy drifts against the record, and the copy +is the one a reader finds first. + +What produces each of them is the half that record cannot show you. + +The first run above returned `0` and the refusing run returned `1`. Both are +completed walks, and the difference between them is in the output rather than +in whether the run worked. + +`2` comes from an invocation the runner cannot act on, rather than from +anything in the tree. Three ways to reach it, each printing to standard error: + + ./lab check README.md + lab check: README.md is not a directory + + ./lab frobnicate + lab: unknown verb "frobnicate" + + ./lab + (the help text, because no verb was given) + +`3` is not a code this command returns. It belongs to the integration-hardware +harness under `internal/hardware`, which is asked for separately, and it is +declared where its only producer is: + + git grep -n 'ExitAskedAndDeliveredNothing = ' -- internal/hardware + internal/hardware/hardware.go:45:const ExitAskedAndDeliveredNothing = 3 + +So the record fixes four codes, `lab` returns three of them, and a caller +keyed on any of the four is reading that record whether or not anybody said so. + +## What a green run does not say + +That it refused nothing. That is the whole of it, and it is narrower than it +looks. It is not a statement that the tree is good, that an experiment worked, +or that anything a record claims is true. An experiment that answered no passes +exactly like one that answered yes, because a no is finished work here. + +Some rules on this board have no mechanism behind them and can have none. The +largest of those is that real data stays on the host it is already on: nothing +in a checkout can tell a number somebody measured from a number they made up. +That is written plainly at the rule in [docs/privacy.md](privacy.md) rather +than left for a green tick to imply otherwise. + +## The notice, the privacy document and the licence + +[NOTICE.md](../NOTICE.md) at the root of the checkout is the intended-use +notice. It places responsibility for lawful use on whoever deploys and runs +this, and it is a notice rather than a control: printing it or shipping it +prevents nothing. + +[docs/privacy.md](privacy.md) is the operator-facing half of what happens to +real data, and this page says the same thing it does deliberately, because a +reader who downloads a tool is not certain to open the other document. Where an +experiment needs real data to answer its question, the data stays on the host it +is already on. Only the measurement is written down. Nothing here uploads, +phones home, or reports usage, and there is no telemetry to turn off because +there is none to turn on. + +There is no licence file in this repository. The question is open, so nothing +here grants permission to reuse what you find, and that is the state rather than +an oversight this page can repair. + +## The record format may change + +This is not a promise of stability. It is a statement of what it is not. + +The experiment record format may change. A change to it is decided in a record +under docs/decisions/ and is not made silently. What happens to the records +already on the default branch on the day it changes is +[docs/decisions/0013-how-the-record-format-changes.md](decisions/0013-how-the-record-format-changes.md), +which is where that was decided, rather than restated here. + +There is no changelog in this repository and no release for one to carry an +entry for, so today a change to the format is announced by the record that +decides it and by nothing else.