Merge pull request 'ADR 0189 collects again; issue 244: a verb with an empty schema cannot be called' (#93) from decision/0189-collects-again into main

This commit was merged in pull request #93.
This commit is contained in:
2026-10-05 12:38:40 +00:00
2 changed files with 94 additions and 0 deletions
@@ -132,6 +132,34 @@ unreferenced.
asserted is the decision and not the registry's behaviour.
- Live: the store's size before and after the first nightly collection, read from the machine.
## Built, withdrawn, and collecting again — 2026-10-04 and 05
**Both halves are live.** The mesh has been deciding since 2026-10-04: the sweep deletes the
manifests no record names, after each build it recorded, bounded to two hundred artifacts and
sixty seconds so no one waits on it. Its first run let go of two hundred and reported one thousand
one hundred and twenty-six left.
**The nightly collector was withdrawn for a day, and this is why.** `while-stopped` named the
module-local id, `store`, while the machine's container is `distribution.store` — a module names
its own resources locally and a declaration names them under the module, which `restart-on` and
`reload-on` are rewritten for and this field was not. The host refuses a declaration naming a
container it does not have **whole**, so the control machine took nothing at all until the step
came out. The namespacing was fixed the same night (mesh-controller#259) and the step is back
(mesh-catalog#57).
**The lesson is about where a test stands, not about the field.** Both sides passed throughout:
the controller's tests read manifests, the host's read hand-written declarations with bare ids,
and nothing composed one and judged the result against what the host accepts. The test that does
now exists, and it is the one that would have caught this in a second.
**And this time the composition was read before anything was sent** — `plan`'s
`distribution.collect` showing `while-stopped: ["distribution.store"]` — which is the check whose
absence caused the outage.
*Where it stands for the live measurement:* at 2026-10-05 14:37 CEST the store is **40G**, the
machine's filesystem 1.1T used of 2.0T, 56%. The collector first fires at 03:30 the following
morning. Deleting a manifest frees no bytes until it does, so that is the number to read against.
## References
- [issue 108 — the registry has no garbage collection](../04-ISSUES/108-the-registry-has-no-garbage-collection-once-it-has-two-doors/00-report.md)
@@ -0,0 +1,66 @@
---
status: open
opened: 2026-10-05
located-in: [mesh-tools, mesh-controller]
fixed-by:
amended-design:
---
# 244 — A verb whose published schema is empty cannot be called through the console, and says only that it needs the argument it will not take
## What was observed
Through the console, 2026-10-05, composing one machine's declaration before pushing it:
```
mesh_describe mesh-controller.plan
→ {"properties": {}, "type": "object"} ... it takes nothing
mesh_call mesh-controller.plan {}
→ plan failed: plan needs "node" ... it takes something
mesh_call mesh-controller.plan {"node": "novox"}
→ plan failed: plan needs "node" ... and not that
```
`mesh-controller.node` describes the same way and behaves the same way. Both are listed in the
node's own instructions as the mesh's verbs, so they are the first thing a session reaches for.
The verb is not broken where it runs: the same `plan novox --json` through the machine's login
shell answers in full. What cannot be done is calling it **through the console**, which the
node's instructions say is the only way to the mesh.
## Why
Not diagnosed past the symptom, and the symptom is specific enough to place it: the published
schema declares no properties, and an argument that is not in the schema does not reach the verb.
So the verb reports a missing argument that the console has dropped, every time, whatever is
sent. The two halves are each defensible — publish a schema, honour it — and together they make
a verb that can only refuse.
## Why it matters beyond this instance
**A tool that cannot be called is worse than one that is absent**, because it is listed. It
appears in `mesh_overview`, it describes itself, and the only thing it will say is that it wants
something it will not accept. A reader has no way to tell that from their own mistake, and the
obvious next move — pass the argument it names — is the one that does not work.
It also marks what the console does not check. Nothing compares a verb's published schema against
the arguments the verb actually requires, so a verb can be published in a shape that makes it
uncallable and nothing says so. That is the shape of an unenforced rule: the schema is believed,
and it is wrong.
## What a fix has to settle
- Where the schema for a seat's verbs comes from, and why these two publish an empty one while
the verbs beside them (`status`, `builds`, `push`) take their arguments and answer.
- Whether the console should refuse to publish a verb whose schema cannot satisfy it, rather than
listing one that can only fail.
- **How it is checked:** every verb the console lists is callable with the arguments its own
schema describes — a test that calls each with its schema's required set and asserts the answer
is not "needs" an argument the schema does not have.
## Worked around, for now
Through `<node>/node-login-shell.execute`, running the controller's own command line on the
machine. That is the path the console exists to replace, so it is a workaround and not an answer.