Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
98eb3aa76f | ||
|
|
91bbe648a8 | ||
|
|
0e7b85f184 | ||
|
|
dac49de6e7 | ||
|
|
0d9208dbbf | ||
|
|
bf0ee7cb25 |
@@ -131,6 +131,22 @@ def main():
|
|||||||
else:
|
else:
|
||||||
seen[number] = name
|
seen[number] = name
|
||||||
|
|
||||||
|
# And decision records, which 155's fix left out: on 2026-10-02 two ADRs numbered 0169 landed
|
||||||
|
# on main from two sessions within the hour, and every check passed.
|
||||||
|
seen_records = {}
|
||||||
|
for path in sorted(glob.glob(os.path.join(ROOT, "02-DECISIONS", "[0-9]*.md"))):
|
||||||
|
name = os.path.basename(path)
|
||||||
|
number = name.split("-", 1)[0]
|
||||||
|
if not number.isdigit():
|
||||||
|
continue
|
||||||
|
if number in seen_records:
|
||||||
|
bad(os.path.join("02-DECISIONS", name),
|
||||||
|
"is numbered %s, and so is %s -- a record's number is how it is cited. Take the next "
|
||||||
|
"free number across main AND every open pull request; the branch that lands last "
|
||||||
|
"renumbers" % (number, seen_records[number]))
|
||||||
|
else:
|
||||||
|
seen_records[number] = name
|
||||||
|
|
||||||
for path in sorted(glob.glob(os.path.join(ROOT, "04-ISSUES", "*", "00-report.md"))):
|
for path in sorted(glob.glob(os.path.join(ROOT, "04-ISSUES", "*", "00-report.md"))):
|
||||||
front = frontmatter(path)
|
front = frontmatter(path)
|
||||||
if front is None:
|
if front is None:
|
||||||
|
|||||||
+20
-1
@@ -7,7 +7,7 @@ reconstructed: false
|
|||||||
extends: 02-DECISIONS/0168-a-converged-machine-is-filtered-by-the-mesh-alone.md
|
extends: 02-DECISIONS/0168-a-converged-machine-is-filtered-by-the-mesh-alone.md
|
||||||
---
|
---
|
||||||
|
|
||||||
# 169. The firewall seat serves its verbs, and a foreign rule set is removed through one of them
|
# 170. The firewall seat serves its verbs, and a foreign rule set is removed through one of them
|
||||||
|
|
||||||
## Context
|
## Context
|
||||||
|
|
||||||
@@ -78,6 +78,25 @@ how the act reaches the machine, recorded on the bus like every other, instead o
|
|||||||
| A container's capabilities reach the runtime and its spec; an unknown name is refused | host tests |
|
| A container's capabilities reach the runtime and its spec; an unknown name is refused | host tests |
|
||||||
| Live | `node-packet-filter.remove@<node>` on the home server and the control node; `node show` reads *the mesh alone* on both; `status` is well |
|
| Live | `node-packet-filter.remove@<node>` on the home server and the control node; `node show` reads *the mesh alone* on both; `status` is well |
|
||||||
|
|
||||||
|
## Built and proven live, 2026-10-02
|
||||||
|
|
||||||
|
> **Progressive insight — 2026-10-02.** The decision stands; these are the facts of its building.
|
||||||
|
> Written as 0169 for three hours and renumbered to 0170: another record took 0169 on main first,
|
||||||
|
> and the check that refuses a shared number covered issues only (now records too).
|
||||||
|
|
||||||
|
Built in mesh-host 68 (`capabilities` on a container), mesh-controller 212 (the seat's three verbs)
|
||||||
|
and 213 (the filter file a module names under `filtering.into` counts as declared for a mount — the
|
||||||
|
module's first build was refused without it), mesh-catalog 216 (the nftables module's runtime and
|
||||||
|
verbs) and mesh-tools 27 (the console lists a node-scoped seat's verbs with their scope and carries the
|
||||||
|
machine; before it, the verbs were live on four machines and unreachable from the console —
|
||||||
|
[issue 199](../04-ISSUES/199-a-node-scoped-seats-verb-could-not-be-called-through-the-console/00-report.md)).
|
||||||
|
Each machine's holder was issued its bus account with `mesh-controller.issue`, the broker node pushed
|
||||||
|
first. At 12:46Z the five rule sets ADR 0168 had named were removed through
|
||||||
|
`node-packet-filter.remove`, three on the home server and two on the control node, each answering
|
||||||
|
with the commands it ran; the next report read *the mesh alone* on all four machines and `status`
|
||||||
|
listed nothing under `filtered`. The live row is read. What it cost on the way is
|
||||||
|
[issue 200](../04-ISSUES/200-the-controllers-answer-to-the-console-is-refused-by-the-bus/00-report.md).
|
||||||
|
|
||||||
## References
|
## References
|
||||||
|
|
||||||
- [ADR 0168](0168-a-converged-machine-is-filtered-by-the-mesh-alone.md), [ADR 0154](0154-the-meshs-own-verbs-are-the-controller-seats-tools.md), [ADR 0159](0159-a-tool-call-names-the-machine-and-a-holder-serves-its-seats-verbs.md), [ADR 0121](0121-a-system-seat-is-named-for-its-scope-and-modules-define-their-own.md)
|
- [ADR 0168](0168-a-converged-machine-is-filtered-by-the-mesh-alone.md), [ADR 0154](0154-the-meshs-own-verbs-are-the-controller-seats-tools.md), [ADR 0159](0159-a-tool-call-names-the-machine-and-a-holder-serves-its-seats-verbs.md), [ADR 0121](0121-a-system-seat-is-named-for-its-scope-and-modules-define-their-own.md)
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
---
|
||||||
|
topic: the mesh
|
||||||
|
status: accepted
|
||||||
|
date: 2026-10-02
|
||||||
|
deciders: jochen
|
||||||
|
reconstructed: false
|
||||||
|
extends: 02-DECISIONS/0016-the-lab.md
|
||||||
|
---
|
||||||
|
|
||||||
|
# 172. The lab is a module, and runs a bed when the mesh asks
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
The lab raises virtual machines and runs the mesh on them, end to end, before a change reaches a real
|
||||||
|
machine ([ADR 0016](0016-the-lab.md)). It runs on one machine of the mesh, the one with the
|
||||||
|
virtualisation it needs. Until now the only way to start a bed there was to sign in to that machine and
|
||||||
|
run the lab's command line by hand, with a dozen environment variables pointing at sibling checkouts.
|
||||||
|
|
||||||
|
Nothing in the mesh could ask for it. An agent working through the mesh's own tools could build,
|
||||||
|
merge and push a change, and could not prove it in the lab first. The operator's direction on
|
||||||
|
2026-10-02: work on another machine goes through a mesh tool, not a shell on it.
|
||||||
|
|
||||||
|
## Considered Options
|
||||||
|
|
||||||
|
1. **Keep the lab a command line on one machine.** Every run is a person, or an agent with a shell on
|
||||||
|
that machine, outside the mesh.
|
||||||
|
2. **The lab is a module.** Assigned to the machine that can run it, serving tools that run a bed
|
||||||
|
against named branches and say how it went.
|
||||||
|
|
||||||
|
## Decision
|
||||||
|
|
||||||
|
**Option 2.**
|
||||||
|
|
||||||
|
- **A `lab` module, assigned where the lab can run**, serves five tools: whether this machine can run
|
||||||
|
beds, run beds against a branch per repository, a run's state, its log, and stopping it.
|
||||||
|
- **A run is the lab's own suite**, against fresh checkouts of the named branches from the mesh's forge,
|
||||||
|
side by side as the lab expects them. It builds what the beds place from those checkouts, as the suite
|
||||||
|
already does. It answers at once with an id, like a build: a bed takes minutes, and a call does not.
|
||||||
|
- **Only branches on the forge are run**, never code handed to the tool. What a run tested is what the
|
||||||
|
forge holds at the commit it names.
|
||||||
|
- **The lab is reached over the mesh only.** Its tools travel the bus, and the module opens no port.
|
||||||
|
- **No grant beyond the mesh's own.** Running a bed is root on the lab's machine, but anyone who can call
|
||||||
|
the mesh's tools can already do worse. The operator's judgement on 2026-10-02.
|
||||||
|
|
||||||
|
## Consequences
|
||||||
|
|
||||||
|
- An agent proves a change in the lab through the mesh, the same way it builds and pushes one.
|
||||||
|
- The lab's machine carries a module whose runtime holds the virtualisation's and the container
|
||||||
|
runtime's sockets, and a toolchain to build the mesh with.
|
||||||
|
- A run's checkouts are its own, so two runs never build from each other's tree. Old ones are removed
|
||||||
|
when their run ends.
|
||||||
|
|
||||||
|
## How this is checked
|
||||||
|
|
||||||
|
| Rule | Checked by |
|
||||||
|
|---|---|
|
||||||
|
| A run checks out exactly the named branches, and reports the commits it tested | the module's tests over a forge fixture, and each run's answer |
|
||||||
|
| A run answers at once, and its state and log follow it to the end | by hand, the first run |
|
||||||
|
| The module opens no port | the composed filter of the lab's machine |
|
||||||
@@ -180,7 +180,8 @@ python3 00-META/checks/index.py fail if stale
|
|||||||
- **0167** — [A membership carries what its module receives, and who the mesh is](0167-a-membership-carries-what-its-module-receives-and-who-the-mesh-is.md)
|
- **0167** — [A membership carries what its module receives, and who the mesh is](0167-a-membership-carries-what-its-module-receives-and-who-the-mesh-is.md)
|
||||||
- **0168** — [A converged machine is filtered by the mesh alone, and the host says what else refuses](0168-a-converged-machine-is-filtered-by-the-mesh-alone.md)
|
- **0168** — [A converged machine is filtered by the mesh alone, and the host says what else refuses](0168-a-converged-machine-is-filtered-by-the-mesh-alone.md)
|
||||||
- **0169** — [A machine joins through the tunnel, and the bus is never public](0169-a-machine-joins-through-the-tunnel-and-the-bus-is-never-public.md)
|
- **0169** — [A machine joins through the tunnel, and the bus is never public](0169-a-machine-joins-through-the-tunnel-and-the-bus-is-never-public.md)
|
||||||
- **0169** — [The firewall seat serves its verbs, and a foreign rule set is removed through one of them](0169-the-firewall-seat-serves-its-verbs.md)
|
- **0170** — [The firewall seat serves its verbs, and a foreign rule set is removed through one of them](0170-the-firewall-seat-serves-its-verbs.md)
|
||||||
|
- **0172** — [The lab is a module, and runs a bed when the mesh asks](0172-the-lab-is-a-module-and-runs-a-bed-when-the-mesh-asks.md)
|
||||||
|
|
||||||
### Its tiers, from the bottom up
|
### Its tiers, from the bottom up
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
---
|
---
|
||||||
layer: to-be
|
layer: to-be
|
||||||
status: in-progress
|
status: in-progress
|
||||||
code: [mesh-lab]
|
code: [mesh-lab, mesh-catalog modules/lab]
|
||||||
updated: 2026-09-11
|
updated: 2026-10-02
|
||||||
decisions:
|
decisions:
|
||||||
|
- 02-DECISIONS/0172-the-lab-is-a-module-and-runs-a-bed-when-the-mesh-asks.md
|
||||||
- 02-DECISIONS/0016-the-lab.md
|
- 02-DECISIONS/0016-the-lab.md
|
||||||
- 02-DECISIONS/0010-delivery.md
|
- 02-DECISIONS/0010-delivery.md
|
||||||
---
|
---
|
||||||
@@ -119,7 +120,25 @@ In order, on a machine with nothing:
|
|||||||
|
|
||||||
6. **Verification**, as above, before anything is raised.
|
6. **Verification**, as above, before anything is raised.
|
||||||
|
|
||||||
## Open
|
## The lab answers the mesh
|
||||||
|
|
||||||
|
*2026-10-02* ([ADR 0172](../../02-DECISIONS/0172-the-lab-is-a-module-and-runs-a-bed-when-the-mesh-asks.md)).
|
||||||
|
Once installed, the lab is also a module: `lab`, assigned to the machine that passed `check`. Its
|
||||||
|
tools run there and nowhere else:
|
||||||
|
|
||||||
|
| tool | does |
|
||||||
|
|---|---|
|
||||||
|
| `lab_check` | the lab's `check`, on this machine |
|
||||||
|
| `lab_run` | fresh checkouts of the named branches from the forge, side by side, then the suite on the named beds; answers with an id |
|
||||||
|
| `lab_status` | where a run is, and how it ended: the commits it tested, passed and failed |
|
||||||
|
| `lab_log` | the run's output so far |
|
||||||
|
| `lab_stop` | ends a run |
|
||||||
|
|
||||||
|
The runtime is a container holding the toolchain the suite builds with. It reaches the
|
||||||
|
virtualisation daemon and the container runtime through their sockets on the machine, so what it
|
||||||
|
raises is what a hand run raises. The prerequisites above stay installed by hand. The module uses
|
||||||
|
them, and never installs them.
|
||||||
|
|
||||||
|
|
||||||
- **Whether the lab's bootstrap may install packages at all**, given that the mesh's rules
|
- **Whether the lab's bootstrap may install packages at all**, given that the mesh's rules
|
||||||
forbid installing by hand. The resolution is probably that the lab's bootstrap *is* the
|
forbid installing by hand. The resolution is probably that the lab's bootstrap *is* the
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ code:
|
|||||||
- mesh-host internal/apply (the service that reflects a rule set)
|
- mesh-host internal/apply (the service that reflects a rule set)
|
||||||
updated: 2026-10-02
|
updated: 2026-10-02
|
||||||
decisions:
|
decisions:
|
||||||
- 02-DECISIONS/0169-the-firewall-seat-serves-its-verbs.md
|
- 02-DECISIONS/0170-the-firewall-seat-serves-its-verbs.md
|
||||||
- 02-DECISIONS/0169-a-machine-joins-through-the-tunnel-and-the-bus-is-never-public.md
|
- 02-DECISIONS/0169-a-machine-joins-through-the-tunnel-and-the-bus-is-never-public.md
|
||||||
- 02-DECISIONS/0168-a-converged-machine-is-filtered-by-the-mesh-alone.md
|
- 02-DECISIONS/0168-a-converged-machine-is-filtered-by-the-mesh-alone.md
|
||||||
- 02-DECISIONS/0167-a-membership-carries-what-its-module-receives-and-who-the-mesh-is.md
|
- 02-DECISIONS/0167-a-membership-carries-what-its-module-receives-and-who-the-mesh-is.md
|
||||||
@@ -767,7 +767,7 @@ tests over a fixture report check the recording, the preview's fates, the status
|
|||||||
predicate. Live: the home server's record names the predecessor's chain as *other* and `status`
|
predicate. Live: the home server's record names the predecessor's chain as *other* and `status`
|
||||||
names the machine until the chain is removed by hand.
|
names the machine until the chain is removed by hand.
|
||||||
|
|
||||||
*2026-10-02, [ADR 0169](../../02-DECISIONS/0169-the-firewall-seat-serves-its-verbs.md):* removing what
|
*2026-10-02, [ADR 0170](../../02-DECISIONS/0170-the-firewall-seat-serves-its-verbs.md):* removing what
|
||||||
the host reports as *other* is reached through the packet filter seat's `remove` verb, an operator's act
|
the host reports as *other* is reached through the packet filter seat's `remove` verb, an operator's act
|
||||||
by name on the bus; the seat also serves `rules` and `reload`, and its holder's runtime declares the
|
by name on the bus; the seat also serves `rules` and `reload`, and its holder's runtime declares the
|
||||||
`NET_ADMIN` capability on the machine's network. See design 33.
|
`NET_ADMIN` capability on the machine's network. See design 33.
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ status: implemented
|
|||||||
code: [mesh-controller, mesh-tools]
|
code: [mesh-controller, mesh-tools]
|
||||||
updated: 2026-10-02
|
updated: 2026-10-02
|
||||||
decisions:
|
decisions:
|
||||||
- 02-DECISIONS/0169-the-firewall-seat-serves-its-verbs.md
|
- 02-DECISIONS/0170-the-firewall-seat-serves-its-verbs.md
|
||||||
- 02-DECISIONS/0160-the-mesh-issues-an-assignments-subjects-and-a-runtime-serves-what-it-is-issued.md
|
- 02-DECISIONS/0160-the-mesh-issues-an-assignments-subjects-and-a-runtime-serves-what-it-is-issued.md
|
||||||
- 02-DECISIONS/0159-a-tool-call-names-the-machine-and-a-holder-serves-its-seats-verbs.md
|
- 02-DECISIONS/0159-a-tool-call-names-the-machine-and-a-holder-serves-its-seats-verbs.md
|
||||||
- 02-DECISIONS/0154-the-meshs-own-verbs-are-the-controller-seats-tools.md
|
- 02-DECISIONS/0154-the-meshs-own-verbs-are-the-controller-seats-tools.md
|
||||||
@@ -172,7 +172,7 @@ seats' schemas beyond the names their manifests already list.
|
|||||||
|
|
||||||
## The firewall seat's verbs, 2026-10-02
|
## The firewall seat's verbs, 2026-10-02
|
||||||
|
|
||||||
[ADR 0169](../../02-DECISIONS/0169-the-firewall-seat-serves-its-verbs.md). The first node-scoped seat
|
[ADR 0170](../../02-DECISIONS/0170-the-firewall-seat-serves-its-verbs.md). The first node-scoped seat
|
||||||
to carry verbs: `node-packet-filter` serves `rules` (the filter as the machine enforces it, nftables
|
to carry verbs: `node-packet-filter` serves `rules` (the filter as the machine enforces it, nftables
|
||||||
and legacy), `reload` (the mesh's own filter from its file) and `remove` (one rule set the mesh did
|
and legacy), `reload` (the mesh's own filter from its file) and `remove` (one rule set the mesh did
|
||||||
not write, named as the host reports it under ADR 0168; refusing the mesh's tables, the runtime's
|
not write, named as the host reports it under ADR 0168; refusing the mesh's tables, the runtime's
|
||||||
|
|||||||
+35
@@ -0,0 +1,35 @@
|
|||||||
|
---
|
||||||
|
status: resolved
|
||||||
|
opened: 2026-10-02
|
||||||
|
located-in: [mesh-tools src/client.ts (toolsOn left node-scoped seats out of the listing), mesh-tools src/mcp.ts (the call resolved the key against that listing)]
|
||||||
|
fixed-by: mesh-tools 27 — node-scoped seats are listed with their scope, the verb requires the machine, and the call carries it in the subject
|
||||||
|
amended-design:
|
||||||
|
---
|
||||||
|
|
||||||
|
# 199 — A node-scoped seat's verb could not be called through the console
|
||||||
|
|
||||||
|
## What was observed
|
||||||
|
|
||||||
|
2026-10-02, the first time a node-scoped seat declared verbs
|
||||||
|
([ADR 0170](../../02-DECISIONS/0170-the-firewall-seat-serves-its-verbs.md)). The packet filter's
|
||||||
|
holder on every machine served `rules`, `reload` and `remove` on the seat's per-machine subjects, and
|
||||||
|
the bus admitted them. The console answered every call with *nothing serves
|
||||||
|
node-packet-filter.rules@<machine>*.
|
||||||
|
|
||||||
|
[Design 33 §4](../../03-DESIGN/01-to-be/33-the-tools-the-mesh-answers.md) says a node-scoped seat's
|
||||||
|
tool carries the node it is asked of, as `<seat>.<verb>@<node>`. The console's listing left
|
||||||
|
node-scoped seats out — the comment said they *wait for a caller naming the node* — but the roles map
|
||||||
|
the console resolves a name against is built from that same listing. So the name never resolved as a
|
||||||
|
seat's verb, fell through to a module's subject nobody served, and the refusal named the wrong cause.
|
||||||
|
|
||||||
|
## Why it matters beyond this instance
|
||||||
|
|
||||||
|
A stated behaviour that did not happen, with a refusal that pointed elsewhere: the seat's verbs were
|
||||||
|
live on four machines and unreachable from the one surface a person uses. It could only be found by a
|
||||||
|
node-scoped seat declaring verbs, which none had.
|
||||||
|
|
||||||
|
## Resolved, 2026-10-02
|
||||||
|
|
||||||
|
mesh-tools 27: node-scoped seats are listed with their scope, their verbs take a required `node`, the
|
||||||
|
call carries it in the subject, and a call without one is refused in words. Tested with a round trip
|
||||||
|
asking one machine's holder and being refused without a machine.
|
||||||
+36
@@ -0,0 +1,36 @@
|
|||||||
|
---
|
||||||
|
status: open
|
||||||
|
opened: 2026-10-02
|
||||||
|
located-in: []
|
||||||
|
fixed-by:
|
||||||
|
amended-design:
|
||||||
|
---
|
||||||
|
|
||||||
|
# 200 — The controller's answer to a long console call is refused by the bus
|
||||||
|
|
||||||
|
## What was observed
|
||||||
|
|
||||||
|
2026-10-02. A `push` of the control node asked through the console came back as *mesh-controller.push
|
||||||
|
did not answer in time. Something is serving it, so this is the tool being slow rather than absent.*
|
||||||
|
The push had run; the machine applied. The bus's log on the control node, at the same moment:
|
||||||
|
|
||||||
|
```
|
||||||
|
[ERR] 10.10.0.1:56030 - cid:4015 - Publish Violation - User "controller",
|
||||||
|
Subject "_INBOX.shanks.mesh-console.WRNO5V9IJ1FX35AU5NRBEB.WRNO5V9IJ1FX35AU5PN1UW"
|
||||||
|
```
|
||||||
|
|
||||||
|
The controller's reply to the console's request was refused: the controller's bus account may not
|
||||||
|
publish to the console's reply inbox. Shorter calls (`status`, `node`, `nodes`) answer; the long ones
|
||||||
|
(`push` of a large machine, `issue`) time out on the console's side although they succeed.
|
||||||
|
|
||||||
|
## Why it matters beyond this instance
|
||||||
|
|
||||||
|
A call that succeeds and is reported as a timeout sends a person to retry what already happened — a
|
||||||
|
second push, a second issue — and reads as the mesh being slow when it is the mesh refusing itself.
|
||||||
|
Whether the inbox prefix the console uses is the one the controller's account is allowed to answer, or
|
||||||
|
the request outlives the inbox subscription, is what a diagnosis has to tell apart.
|
||||||
|
|
||||||
|
## Open questions
|
||||||
|
|
||||||
|
- Which reply inboxes may the controller's account publish to, and which does the console request on?
|
||||||
|
- Does a reply after the requester's timeout count as a violation, or is the prefix itself refused?
|
||||||
Reference in New Issue
Block a user