Recovery of novox (2026-10-29). de-spiegel is held on novox (an unresolved config value), and the hold spread: mailu ("it reads what de-spiegel contributes"), then route-proxy, nftables and postgres were held too, so every site behind route-proxy is down. The assigner kept wants back for de-spiegel and link2pay ("rule 5: no provider of route runs healthy") and photos ("no provider of s3-bucket runs healthy"). The operator: one module should never block another. mesh-sdk #128 stopped the spread to consumers and collectors but kept dependents() in compose/frombus/answer.go, which held the provider of what a held module contributes to, transitively.
What changes
mesh-sdk: dependents, heldBehind and the held-behind path in heldAsToday go. A held module offers the others what it last offered (its contributes and listens from the manifest of the build it runs), and only the provision that does not compose is left out.
mesh-delivery (assigner): rule 5 holds no want; an unhealthy provider is recorded and said once. Rule 3 (the canary) skips a first want only for a node that does not run the module; moves of a running module keep the canary order.
mesh-host: pins mesh-sdk dbcaa29 (the composer it runs) and expects the proxy composed beside a held de-spiegel.
This departs from ADR 0429 §6 / design 47 (canary first and every provider healthy for every want); a decision record follows.
Quick fix or thorough fix
Quick: drop dependents() and skip the two rules for these three modules.
Thorough, chosen: no spread anywhere, the held module's offer read from what runs, per-provision leave-out, and no want held back by an unhealthy provider for any module.
How it is checked
Rows added in existing tests (no new test functions): de-spiegel held while mailu and the proxy compose; the assigner gives de-spiegel and link2pay wants with route-proxy down, photos a want with minio unhealthy, and an old-form running node still waits for the canary.
Live: on novox mailu, route-proxy, nftables and postgres compose; de-spiegel, link2pay and photos get wants; the sites behind route-proxy answer (de-spiegel's own names wait for its config value).
Group: delivery group fix/1960-no-hold-spread (mesh-sdk, mesh-delivery, mesh-host).
## Why
Recovery of novox (2026-10-29). de-spiegel is held on novox (an unresolved config value), and the hold spread: mailu ("it reads what de-spiegel contributes"), then route-proxy, nftables and postgres were held too, so every site behind route-proxy is down. The assigner kept wants back for de-spiegel and link2pay ("rule 5: no provider of route runs healthy") and photos ("no provider of s3-bucket runs healthy"). The operator: one module should never block another. mesh-sdk #128 stopped the spread to consumers and collectors but kept `dependents()` in compose/frombus/answer.go, which held the provider of what a held module contributes to, transitively.
## What changes
- mesh-sdk: `dependents`, `heldBehind` and the held-behind path in `heldAsToday` go. A held module offers the others what it last offered (its contributes and listens from the manifest of the build it runs), and only the provision that does not compose is left out.
- mesh-delivery (assigner): rule 5 holds no want; an unhealthy provider is recorded and said once. Rule 3 (the canary) skips a first want only for a node that does not run the module; moves of a running module keep the canary order.
- mesh-host: pins mesh-sdk dbcaa29 (the composer it runs) and expects the proxy composed beside a held de-spiegel.
- This departs from ADR 0429 §6 / design 47 (canary first and every provider healthy for every want); a decision record follows.
## Quick fix or thorough fix
- Quick: drop `dependents()` and skip the two rules for these three modules.
- Thorough, chosen: no spread anywhere, the held module's offer read from what runs, per-provision leave-out, and no want held back by an unhealthy provider for any module.
## How it is checked
- Rows added in existing tests (no new test functions): de-spiegel held while mailu and the proxy compose; the assigner gives de-spiegel and link2pay wants with route-proxy down, photos a want with minio unhealthy, and an old-form running node still waits for the canary.
- Live: on novox mailu, route-proxy, nftables and postgres compose; de-spiegel, link2pay and photos get wants; the sites behind route-proxy answer (de-spiegel's own names wait for its config value).
Group: delivery group fix/1960-no-hold-spread (mesh-sdk, mesh-delivery, mesh-host).
Reviewed-by: rev-hold@shanks at dbcaa29a1a386c5b6f9e45f0f19fb493ca3a89f2
On novox on 2026-10-29 de-spiegel, held for an unset config value, held mailu, route-proxy and nftables ("it reads
what de-spiegel contributes or listens on"), and mailu held postgres. mesh-sdk #128 (issue 1126) stopped the hold
spreading to consumers and collectors but kept dependents(): the provider of what a held module contributes to, and
the filter where it listens, were held behind it, transitively.
- Quick fix: drop dependents() alone. The filter and the proxy would then read a held module's new manifest, which
may not be what runs (review of the apply, round 2).
- Thorough fix, chosen: no module is held behind another, in the apply and in the answer (dependents, heldBehind and
the held-behind path of heldAsToday removed). A held module offers what it last offered: its contributions and
listens from its manifest at the build its own composition runs at (ranOf), else the manifest it is composed from.
Only the part of its offer that does not compose is left out, by provision (culprit): de-spiegel's routes leave
route-proxy without them, while mailu keeps its smtp account; the hold names it.
Tests: rows changed in the existing answer test (de-spiegel held, mailu and proxy composed; site held, the filter
composed with what runs; leave-out by provision). No new test function.
Deliverynovox/mesh-sdk@dbcaa29a1a38 — merged since 2026-10-29T15:35:26Z
Source — source.main moved to novox/mesh-sdk@307b6234e6. Whether it reached the machines is mesh-delivery-view's, derived from source.main, the wants and the haves.
composed together: pass — the checker's verdict at the members' heads (f0569710e57f)
Transitions
2026-10-29 15:05 (new) → proposed (announced): novox/mesh-sdk#131's head announced
2026-10-29 15:05 proposed → ready (accepted): a verdict names this commit and the trunk's head as its trunk commit, the gate passed or warned, and the repository's own check did not fail; or the check gates no merge (check-gates-merge false)
2026-10-29 15:35 ready → merged (merged): on the trunk its modules follow: merged there
The commit's note under refs/notes/mesh-plan keeps every transition of this change up to its merge: git log --notes=mesh-plan.
<!-- mesh-delivery:view -->
**Delivery** `novox/mesh-sdk@dbcaa29a1a38` — **merged** since 2026-10-29T15:35:26Z
**Source** — source.main moved to novox/mesh-sdk@307b6234e6bb. Whether it reached the machines is `mesh-delivery-view`'s, derived from `source.main`, the wants and the haves.
**Group** `fix/1960-no-hold-spread`: novox/mesh-controller@020c6a99b035, novox/mesh-delivery@d54eab4052d1, novox/mesh-host@ece607e21522, novox/mesh-sdk@dbcaa29a1a38
- composed together: pass — the checker's verdict at the members' heads (f0569710e57f)
**Transitions**
- 2026-10-29 15:05 (new) → proposed (announced): novox/mesh-sdk#131's head announced
- 2026-10-29 15:05 proposed → ready (accepted): a verdict names this commit and the trunk's head as its trunk commit, the gate passed or warned, and the repository's own check did not fail; or the check gates no merge (check-gates-merge false)
- 2026-10-29 15:35 ready → merged (merged): on the trunk its modules follow: merged there
The commit's note under `refs/notes/mesh-plan` keeps every transition of this change up to its merge: `git log --notes=mesh-plan`.
mesh-delivery: the operator's Approve of this pull request's merge into main is ended: a head it covers moved on: the heads it covers are no longer the group's heads. Nothing is merged on it. The operator is asked again once the checks pass at the new head.
mesh-delivery: the operator's Approve of this pull request's merge into main is ended: a head it covers moved on: the heads it covers are no longer the group's heads. Nothing is merged on it. The operator is asked again once the checks pass at the new head.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Why
Recovery of novox (2026-10-29). de-spiegel is held on novox (an unresolved config value), and the hold spread: mailu ("it reads what de-spiegel contributes"), then route-proxy, nftables and postgres were held too, so every site behind route-proxy is down. The assigner kept wants back for de-spiegel and link2pay ("rule 5: no provider of route runs healthy") and photos ("no provider of s3-bucket runs healthy"). The operator: one module should never block another. mesh-sdk #128 stopped the spread to consumers and collectors but kept
dependents()in compose/frombus/answer.go, which held the provider of what a held module contributes to, transitively.What changes
dependents,heldBehindand the held-behind path inheldAsTodaygo. A held module offers the others what it last offered (its contributes and listens from the manifest of the build it runs), and only the provision that does not compose is left out.dbcaa29(the composer it runs) and expects the proxy composed beside a held de-spiegel.Quick fix or thorough fix
dependents()and skip the two rules for these three modules.How it is checked
Group: delivery group fix/1960-no-hold-spread (mesh-sdk, mesh-delivery, mesh-host).
Reviewed-by: rev-hold@shanks at
dbcaa29a1aOn novox on 2026-10-29 de-spiegel, held for an unset config value, held mailu, route-proxy and nftables ("it reads what de-spiegel contributes or listens on"), and mailu held postgres. mesh-sdk #128 (issue 1126) stopped the hold spreading to consumers and collectors but kept dependents(): the provider of what a held module contributes to, and the filter where it listens, were held behind it, transitively. - Quick fix: drop dependents() alone. The filter and the proxy would then read a held module's new manifest, which may not be what runs (review of the apply, round 2). - Thorough fix, chosen: no module is held behind another, in the apply and in the answer (dependents, heldBehind and the held-behind path of heldAsToday removed). A held module offers what it last offered: its contributions and listens from its manifest at the build its own composition runs at (ranOf), else the manifest it is composed from. Only the part of its offer that does not compose is left out, by provision (culprit): de-spiegel's routes leave route-proxy without them, while mailu keeps its smtp account; the hold names it. Tests: rows changed in the existing answer test (de-spiegel held, mailu and proxy composed; site held, the filter composed with what runs; leave-out by provision). No new test function.Delivery
novox/mesh-sdk@dbcaa29a1a38— merged since 2026-10-29T15:35:26ZSource — source.main moved to novox/mesh-sdk@307b6234e6. Whether it reached the machines is
mesh-delivery-view's, derived fromsource.main, the wants and the haves.Group
fix/1960-no-hold-spread: novox/mesh-controller@020c6a99b0, novox/mesh-delivery@d54eab4052, novox/mesh-host@ece607e215, novox/mesh-sdk@dbcaa29a1aTransitions
The commit's note under
refs/notes/mesh-plankeeps every transition of this change up to its merge:git log --notes=mesh-plan.mesh-delivery: the operator's Approve of this pull request's merge into main is ended: a head it covers moved on: the heads it covers are no longer the group's heads. Nothing is merged on it. The operator is asked again once the checks pass at the new head.