Merge pull request 'Issue 325 and ADR 0261: an assignment made while its module's build runs is kept pending' (#214) from fix/325-assign-says-why-a-module-is-not-there into main
This commit was merged in pull request #214.
This commit is contained in:
+4
-1
@@ -172,10 +172,13 @@ every other domain has something to run on. **Recorded by** the controller and t
|
||||
|
||||
**Purpose.** To decide, send and apply what each node runs, and to say why.
|
||||
**Recorded by** the controller's `inventory` database. **Upstream of** Provisioning, Connectivity, Data
|
||||
and Health and repair. **Decided by** ADR 0100, 0126, 0176, 0181, 0207, 0221, 0252.
|
||||
and Health and repair. **Decided by** ADR 0100, 0126, 0176, 0181, 0207, 0221, 0252, 0261.
|
||||
**Uses:** node, machine, module, seat, setting (Module), manifest (Module).
|
||||
|
||||
- **assignment** — a module put on a node, with the settings that node gives it.
|
||||
- **pending assignment** — an assignment of a module whose build is still running, kept, and made by the
|
||||
controller when that build registers the module, or ended with why when it does not
|
||||
([ADR 0261](../02-DECISIONS/0261-an-assignment-made-while-its-modules-build-runs-is-kept-pending-and-the-controller-makes-it.md)).
|
||||
- **scope** — where a seat or a claim holds: `node`, `site` or `mesh`. `node` is also the scope's
|
||||
value in a manifest.
|
||||
- **capacity** — how many holders a seat takes. A capacity-1 seat is exclusive. A higher-capacity seat
|
||||
|
||||
+99
@@ -0,0 +1,99 @@
|
||||
---
|
||||
topic: what runs on it
|
||||
status: accepted
|
||||
date: 2026-10-08
|
||||
deciders: jochen
|
||||
reconstructed: false
|
||||
---
|
||||
|
||||
# 261. An assignment made while its module's build runs is kept pending, and the controller makes it
|
||||
|
||||
## Context
|
||||
|
||||
Since issue 300's fix, a merge that adds a module asks the build seat for it, and the module is registered
|
||||
when the build's outcome is taken in, minutes later. On 2026-10-08 `assign` was called in that window and
|
||||
answered "no module of that name"
|
||||
([issue 325](../04-ISSUES/325-assign-said-no-such-module-while-its-build-ran/00-report.md)). The controller
|
||||
kept no record of a build it had asked for until the outcome came back, so it could not tell a build in
|
||||
flight from a typo.
|
||||
|
||||
Until now an assignment was always made at the person's act, and refused there if it could not be made.
|
||||
Keeping it for later means the controller makes an assignment after the act, without the person present.
|
||||
That is a new kind of act, and it needs a rule.
|
||||
|
||||
## Considered Options
|
||||
|
||||
1. **Refuse, and say the build is running.** The person waits and calls `assign` again. Rejected: it brings
|
||||
back the second act by hand that issue 300 removed, and nothing reminds the person to make it.
|
||||
2. **Keep the assignment only when asked** (an argument such as `wait`). Rejected: a person who knows to ask
|
||||
for it already knows the build is running; the one who does not is the one this is for.
|
||||
3. **Keep the assignment by default while a build is in flight, and have the controller make it when the
|
||||
build registers the module.** Chosen.
|
||||
|
||||
## Decision
|
||||
|
||||
1. **The controller keeps every build request it makes**, once the ask is made and never before, with who
|
||||
asked it (a merge, a plan, a person's `build`, `assign`), the repository, the directory, the branch and
|
||||
the merge's commit. A request whose ask could not be handed over is kept as not asked, with why. One
|
||||
handed over whose asker stopped waiting is kept as asked with its outcome unknown: the build may still
|
||||
run, so it reads as in flight until its outcome or the two-hour bound. A request is kept for 30 days.
|
||||
2. **`assign` of a module the catalogue does not hold says which case it is in:**
|
||||
- *a build is in flight*: a kept request for the module's directory has no outcome yet (within two
|
||||
hours of asking), or its build succeeded within the last five minutes and is being registered;
|
||||
- *known, not built*: there is a kept request, and its build failed, was not registered, gave no outcome,
|
||||
or could not be asked;
|
||||
- *unknown*: no module registered and no kept request by that name. The answer claims only that, and
|
||||
names the closest names.
|
||||
3. **In the first case the assignment is kept as a pending assignment**, with no argument needed. In the
|
||||
second, `assign` refuses, and its `build` argument asks for the build from where the last request came
|
||||
from and keeps the assignment pending on it. If a pending assignment of that module to that machine is
|
||||
already waiting, it is made to wait for the new build; no build is asked that nothing waits on.
|
||||
4. **The controller makes a pending assignment by the same act as a person's `assign`**, with the same
|
||||
checks, when the build registers the module. It first takes the machine's hold and claims the pending
|
||||
assignment, so a withdrawal cannot land in between. A refusal then ends it as refused. Making it sends
|
||||
nothing, as a person's `assign` sends nothing.
|
||||
5. **A pending assignment never waits silently.** It expires with the builder's own words when its build
|
||||
fails or is not registered, and with a sentence when no outcome comes within two hours. The controller
|
||||
settles pending assignments on a tick of its own, every minute, and on each build's outcome. A read,
|
||||
such as `status`, settles nothing.
|
||||
6. **An assignment that was not made is a condition** (`assignment-not-made`, a warning the operator
|
||||
resolves), one per pending assignment: its id names the row, so one row's condition is never cleared for
|
||||
another's. It stays open until it is answered: the module is assigned to that machine later; a newer
|
||||
pending assignment of it is open or was made (a newer one that also ended unmade answers nothing, and is
|
||||
its own condition); or a person takes it back with `unassign`, which takes back every one of that module
|
||||
on that machine.
|
||||
7. **`unassign` withdraws a waiting pending assignment.** It is refused, and says so, while the controller
|
||||
is making one. A claim left by a controller that stopped is settled by the tick after five minutes, from
|
||||
what the mesh holds: made if the module is assigned there, else waiting again. Ended pending assignments
|
||||
are deleted after 30 days, but one raised as a condition only once that condition is cleared. A machine's
|
||||
pending assignments are deleted with it, and the tick clears any of their conditions still open.
|
||||
|
||||
## Consequences
|
||||
|
||||
- One kind of assignment is now made by the controller, not at the person's act. The person's intention is
|
||||
still the cause: nothing is assigned that a person did not ask for.
|
||||
- `status` lists open pending assignments (and is not well while one is open) and those ended in the last
|
||||
day; the condition carries an assignment not made until it is answered.
|
||||
- The controller cannot read the forge. A module directory that no merge announced since this record and no
|
||||
person asked to build still reads as unknown; the refusal says how to build it.
|
||||
- A build being registered reads as in flight for five minutes after its outcome. A build recorded and never
|
||||
registered reads so only after that.
|
||||
|
||||
## How it is checked
|
||||
|
||||
- mesh-controller `cmd/mesh-controller/pending_test.go`: each case of point 2; the claim under the hold and
|
||||
the refused withdrawal (points 4, 7); a stale claim settled both ways (point 7); a failed ask that never
|
||||
reads as in flight, a build no longer waited for that does, and the asker kept (point 1); `build`
|
||||
re-pointing a waiting assignment (point 3); a status read that settles nothing, the tick that does, and a
|
||||
silent build expiring (point 5); the condition raised, cleared by `unassign` or a later assignment, and a
|
||||
second assignment not made keeping its own condition (point 6); ended rows deleted after 30 days, a
|
||||
raised one kept until its condition clears, and a removed machine's conditions cleared (point 7).
|
||||
- mesh-controller `internal/inventory/pending_test.go`: a pending assignment goes with its machine.
|
||||
- `TestReplay325`, R325 in mesh-lab's replays register: the live case of issue 325.
|
||||
|
||||
## References
|
||||
|
||||
- [Issue 325](../04-ISSUES/325-assign-said-no-such-module-while-its-build-ran/00-report.md); issue 300.
|
||||
- novox/mesh-controller#150, novox/mesh-lab#68.
|
||||
- [ADR 0207](0207-a-module-depends-on-the-node-seats-that-apply-its-resources.md): modules assigned together
|
||||
are judged together, so an act naming a module not registered is refused whole.
|
||||
@@ -4,6 +4,7 @@ status: in-progress
|
||||
code: [mesh-controller, mesh-host, mesh-tools, mesh-catalog, mesh-sdk, mesh-lab]
|
||||
updated: 2026-10-08
|
||||
decisions:
|
||||
- 02-DECISIONS/0261-an-assignment-made-while-its-modules-build-runs-is-kept-pending-and-the-controller-makes-it.md
|
||||
- 02-DECISIONS/0254-a-wait-for-a-persons-new-login-is-not-a-gates-failure-and-each-module-of-a-send-keeps-its-own-pass.md
|
||||
- 02-DECISIONS/0253-a-condition-says-itself-to-the-operator-in-plain-words-beside-its-summary.md
|
||||
- 02-DECISIONS/0240-a-module-says-how-it-is-healthy-and-the-node-engine-judges-it.md
|
||||
@@ -177,6 +178,14 @@ controller's suite raises, which fails a kind without words.
|
||||
change `reopened`.
|
||||
- **`status`** lists open conditions first, urgent before warning, oldest first, silenced ones marked
|
||||
with their expiry. The all-well sentence requires none open, silenced included.
|
||||
|
||||
**Changed 2026-10-08** ([ADR 0261](../../02-DECISIONS/0261-an-assignment-made-while-its-modules-build-runs-is-kept-pending-and-the-controller-makes-it.md)): `status` also
|
||||
lists every open pending assignment (one kept while its module's build runs) and every one ended in the
|
||||
last day, with what ended it. The all-well sentence requires none open. An assignment that was not made
|
||||
(expired or refused) is the condition `assignment-not-made`, a warning the operator resolves, open until
|
||||
the module is assigned there, assigned again, or taken back with `unassign`. The controller settles
|
||||
pending assignments on its own one-minute tick and on each build's outcome; reading `status` settles
|
||||
nothing.
|
||||
- **ADR 0224's provider standing** is the first kind: `provider-failing`, raised by the provider's
|
||||
event, cleared by its recovery, its silence after thirty minutes the row S8 below.
|
||||
- **Retirement** ([ADR 0230](../../02-DECISIONS/0230-a-consumer-the-mesh-stops-asking-for-is-retired-and-deleted-only-by-a-person.md))
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
---
|
||||
status: located
|
||||
opened: 2026-10-08
|
||||
located-in: [mesh-controller cmd/mesh-controller (acts.go, the assign act; upgrades.go, the merge's build request), mesh-controller internal/inventory (the build request and pending assignment records)]
|
||||
fixed-by: mesh-controller#150, mesh-lab#68
|
||||
replay: R325
|
||||
amended-design: 03-DESIGN/01-to-be/45-a-core-that-cannot-fail-silently.md
|
||||
---
|
||||
|
||||
# 325. Assign said "no module of that name" while the module's build ran
|
||||
|
||||
## What was observed
|
||||
|
||||
On 2026-10-08 the catalogue's pull request adding a new module, `sensors`, merged. Since issue 300's fix, a
|
||||
merge that adds a module asks the build seat for it, and the build registers the module when its outcome is
|
||||
taken in. About a minute after the merge, `mesh-controller.assign {node: "g14", module: "sensors"}`
|
||||
answered:
|
||||
|
||||
> no module of that name: sensors
|
||||
|
||||
A few minutes later the same call worked. The build had not finished the first time.
|
||||
|
||||
The operator read the answer as "you forgot to register the module" and asked whether `assign` could offer
|
||||
to register it when it is not found.
|
||||
|
||||
## Why it is an issue
|
||||
|
||||
The answer was true and misleading. The controller had itself asked for the build a minute earlier, and it
|
||||
answered as if it had never heard of the module. It kept no record of a build it had asked for until the
|
||||
outcome came back, so `assign` could not tell three different situations apart:
|
||||
|
||||
1. a build is in flight, and the module will exist in minutes;
|
||||
2. the module's directory is known (a merge or a person asked for its build) and it was never registered,
|
||||
because the build failed, was not registered, never answered, or could not be asked;
|
||||
3. the name is unknown: a typo, or a module in no repository the mesh builds from.
|
||||
|
||||
Each needs a different act from the person, and the one answer given fitted only the third. Issue 300
|
||||
removed the second act by hand from adding a module; this answer put it back, as a wait and a retry that
|
||||
nothing told the person to make.
|
||||
|
||||
## Fix
|
||||
|
||||
[ADR 0261](../../02-DECISIONS/0261-an-assignment-made-while-its-modules-build-runs-is-kept-pending-and-the-controller-makes-it.md) decides it; mesh-controller#150 builds
|
||||
it.
|
||||
|
||||
**The controller keeps every build request it makes, once the ask is made, and `assign` reads them.** A
|
||||
request is kept with who asked it (a merge, a plan, a person's `build`, `assign`), its repository,
|
||||
directory and branch, and the merge's commit. A request whose ask could not be handed over is kept as not
|
||||
asked, so it never reads as a build in flight; one whose asker stopped waiting may still run, and reads as
|
||||
in flight until its outcome or two hours. Requests are kept for 30 days. The
|
||||
outcome is the `build` record with the same id.
|
||||
|
||||
An assignment of a module the catalogue does not hold now says which case it is in:
|
||||
|
||||
- **A build is in flight** (no outcome yet, within two hours of asking; or built within the last five
|
||||
minutes and being registered): "sensors is not registered yet: it is being built from
|
||||
novox/mesh-catalog@3da80a4b (modules/sensors) since 15:38:17, as build build-…; it can be assigned once
|
||||
that build registers it". The assignment is kept as a **pending assignment**, and the controller makes
|
||||
it, by the same act as a person's `assign`, when the build registers the module. It claims the pending
|
||||
assignment under the machine's hold first, so an `unassign` cannot land in between; an `unassign` while
|
||||
it is being made is refused and says so. If the build fails, is not registered, or says nothing within
|
||||
two hours, the pending assignment expires with the build's own words and raises the condition
|
||||
`assignment-not-made`, one per pending assignment. That condition stays open until the module is assigned
|
||||
there, assigned again and pending or made, or taken back with `unassign`. The controller settles pending assignments on its own one-minute tick and on
|
||||
each build's outcome; reading `status` settles nothing. `status` lists open pending assignments and those
|
||||
ended in the last day, and is not well while one is open.
|
||||
- **Known, not built**: said with what came of the last request, and refused. The answer offers `build`,
|
||||
and `assign` with the new argument `build: "true"`, which asks for the build from where the last request
|
||||
came from and keeps the assignment pending on it. If a pending assignment is already waiting, it is made
|
||||
to wait for the new build.
|
||||
- **Unknown**: refused as "no module of that name", as before. The answer says only what was looked at (the
|
||||
catalogue, and the build requests kept for 30 days), and adds the closest names.
|
||||
|
||||
Making a pending assignment sends nothing, as a person's `assign` sends nothing; `push` sends it.
|
||||
|
||||
Several modules in one act are judged together (ADR 0207). So an act naming a module that is not registered
|
||||
is refused whole, with each module's case said, and nothing is kept pending.
|
||||
|
||||
**Not covered.** The controller cannot read the forge, so a module directory that no merge announced since
|
||||
this fix, and no person asked to build, still reads as unknown. The refusal says how to build it.
|
||||
|
||||
## How it is checked
|
||||
|
||||
- `TestReplay325` replays the live case: a merge adds `modules/sensors`, `assign laptop sensors` runs
|
||||
before the build's outcome, and then the outcome registers it. The assignment must say the build and be
|
||||
made when the build lands. On the commit before the fix it fails with "no module of that name: sensors".
|
||||
It is R325 in mesh-lab's replays register (mesh-lab#68).
|
||||
- `cmd/mesh-controller/pending_test.go` and `internal/inventory/pending_test.go` hold the tests ADR 0261
|
||||
lists under *How it is checked*: one per case, and one per point of its decision.
|
||||
|
||||
To-be 45 §2 is amended: what `status` lists, and the new condition.
|
||||
Reference in New Issue
Block a user