ADR 0230: a consumer the mesh stops asking for is retired, and deleted only by a person

The operator's decision replaces ADR 0229's hourly release: the mesh no longer
finishes alone what its bound exists to question. To-be 45 and the module
protocol amended; 0229 and 0224 point to where their mechanism moved.
This commit is contained in:
jochen
2026-10-06 14:40:40 +02:00
parent 4cdee43f69
commit 3904ef88bf
6 changed files with 252 additions and 7 deletions
@@ -60,6 +60,13 @@ has forgotten it.
- **No secret travels.** The error is the provider's own text with the consumer's password removed, as
its log line already is.
> **The mechanism changed — 2026-10-06, by [ADR 0230](0230-a-consumer-the-mesh-stops-asking-for-is-retired-and-deleted-only-by-a-person.md).**
> What stands: a consumer the mesh stopped asking for is announced recovered. What moved: a consumer the
> provider still holds is no longer withdrawn on the first pass that misses it; it is said recovered when
> it is *retired*, after five passes or a person's approval. A provider says what it retires, approves,
> re-enables and deletes on an event of its own, `provisioner.retirement`, permitted the same way as the
> two events here.
**2. Every provider may say it, whatever its manifest lists.** The permission to publish the two events
is derived for every module that receives contributions; no manifest declares them. A provider whose
manifest forgot them would otherwise have its announcement refused by the bus and fail as silently as
@@ -134,6 +134,13 @@ let go every hour, said. A consumer asked for again is kept. **The brake stops t
intention**: an unassignment of many completes without a hand, a mistaken one costs at most one consumer
an hour while the operator is told — and withdrawal destroys no data since issue 241.
> **Replaced in part — 2026-10-06, by [ADR 0230](0230-a-consumer-the-mesh-stops-asking-for-is-retired-and-deleted-only-by-a-person.md).**
> The paragraph above no longer stands; the operator decided against releasing one consumer an hour.
> A consumer the mesh stops asking for is now *retired* — disabled, reversibly, and marked to delete —
> once the same result holds for five passes; a set of more than three, or more than half of those held,
> waits for a person's `retire approve`; and only `cleanup delete` deletes. Every other decision in this
> record stands. The *how it is checked* row for the brake is replaced by 0230's.
**What Phase 1 decided while building** — [issue 270](../04-ISSUES/270-phase-1-watches-signals-that-come-later-and-hears-only-the-controllers-faults/00-report.md)'s
decisions 1 to 6: the condition history as a bucket of its own, the events' shape, a key's last token,
the probe DW, which deleted consumers are said, and the bounds the design left to the build — is taken
@@ -0,0 +1,194 @@
---
topic: the mesh
status: accepted
date: 2026-10-06
deciders: jochen
reconstructed: false
extends: 02-DECISIONS/0229-the-cores-order-is-a-lease-the-store-remembers-and-an-epoch-a-machine-is-sent-once-it-reads-one.md
---
# 230. A consumer the mesh stops asking for is retired, and deleted only by a person
## Context
**This is the operator's decision, taken on 2026-10-06**, the day the withdrawal brake of
[ADR 0229](0229-the-cores-order-is-a-lease-the-store-remembers-and-an-epoch-a-machine-is-sent-once-it-reads-one.md)
was built. The model below — the three states, the stable result, the threshold that hands the act to a
person, the cleanup verbs, the thirty-day reminder — is the operator's direction, recorded here as given.
What the record adds is the evidence it rests on and the facts the build had to settle.
**What the brake did.** A provider's loop ([ADR 0224](0224-a-provider-that-keeps-failing-a-consumer-is-a-problem-the-controller-reports.md),
in the Go providers and the SDK's) that would withdraw more than one consumer in a pass, or more than
half of those it held, withdrew nothing and announced each kept consumer as failing; then, while the mesh
went on not asking, it let one go every hour, with no person involved. So a mistaken unassignment of
seven consumers still completed by itself in seven hours, and a person who did not read `status` in that
time lost all seven.
**What withdrawal does today**, since [issue 241](../04-ISSUES/241-one-unreadable-grants-file-dropped-every-database-on-the-control-node/00-report.md):
nothing a provider withdraws is destroyed in the providers that issue names — the relational database
locks the role and keeps the database; the SQL Server provider disables the login; the document store
strips the user's roles; the object store revokes the key and keeps the bucket; the mail server disables
the mailbox; the forge prohibits the login; the analytics site is kept. But three things were still
wrong:
- **The identity provider deleted the client.** Its withdrawal was a `DELETE` of the consumer's client,
outside issue 241's list; the secret, redirects and mappers went with it.
- **Nothing recorded that a withdrawal happened, or when.** A locked role is indistinguishable from one
an operator locked by hand. On the home server one consumer's login has been locked by an earlier
withdrawal, its database kept, with no record of when or why; on the control node six databases set
aside during issue 241's recovery sit under a dated name, which is the only record of them.
- **A restart forgot.** The loop withdrew only what *this process* had made. A consumer unassigned while
its provider was down was never withdrawn at all — its login stayed open, indefinitely.
**And nothing ever deleted anything**, so withdrawn data accumulates with no surface that says it is
there.
## Considered Options
1. **Keep the hourly release.** Rejected by the operator: it lets the mesh finish, unattended, an act the
bound exists to question. Many changes at once means a person is at work, and that person can confirm
once.
2. **Delete after a grace period.** Rejected: a timer is the mesh acting alone again, only later; the
loss in issue 241 was data nobody had decided to lose.
3. **Withdraw at once, below the bound, as before.** Rejected: one pass is one read of one file, and
issue 241 was one bad read. A result has to hold before it is acted on.
4. **Three states — active, retired, deleted — where retiring is reversible and automatic only when
stable and small, and deleting is always a person's act through the controller, executed by the
provider.** Chosen.
## Decision
**1. A consumer is ACTIVE, RETIRED or DELETED.**
- **Retired**: the mesh stopped asking for it. The provider **disables its access** — reversibly — and
**marks its login and data "to delete", with when and why**. Nothing is deleted. Asked for again, the
provider's ordinary create re-enables it at once, as it was, and clears the mark.
- **Deleted**: only through `cleanup delete`, after a person approved it.
**2. What "disable" means, per backend** — each the smallest reversible switch that stops the consumer
reaching its data, keeping everything else:
| Provider | Retired is | Kept | Mark |
|---|---|---|---|
| postgres | the role set `NOLOGIN`, its open sessions ended | the database, its owner and grants, every byte; `CONNECT` is not revoked and the database still accepts connections, so the nightly dump still backs it up | the role's comment, a JSON object naming the consumer's machine, when and why |
| keycloak, the identity provider | the client `enabled: false` — the server refuses its authorization and token requests | its secret, redirects, mappers and every other setting | the client's attributes `mesh.retired` and `mesh.retired-why` |
| the TypeScript providers | what their `remove` already does since issue 241 (a key revoked, a login disabled, roles stripped, a mailbox disabled), until each adds a `retire` of its own | as issue 241 lists | none yet: a TypeScript provider does not list or delete retired consumers until its adapter can |
A database renamed aside — by postgres's `postgres_retire_database` tool or by hand, the
`<name>_deleted_<date>` form — is listed beside the retired consumers, retired since that date, so a
person sees it and can delete it.
**3. Stable removals.** A provider retires a consumer only after it has seen **the same set** of
consumers no longer asked for in **five consecutive passes** that read the contributions file. A pass
that could not read it is not a result and starts the count again; so does a different set. At the loop's
pass interval of **five seconds**, retirement comes **twenty seconds** after the first pass that saw the
set — twenty to twenty-five seconds after the file changed. **Additions and changes to an existing
consumer — create, rotate — act on the first pass and are never delayed.**
**4. Too many is a person.** A stable set of **more than three consumers, or of more than half of those
the provider holds where it holds more than one**, retires nothing. The provider **waits**: it says so in
its journal and announces it, the controller raises an **urgent** condition listing what would be
retired, and it waits for `retire approve <node> <provider-module>` or `retire reject <node>
<provider-module>`. **An unassignment the controller itself made goes through the same threshold** — the
operator's words: *too many changes means a human is actively working on it*, so one confirmation. The
bound is the old brake's half, with three in place of one: one, two or three consumers of a larger
provider go without a hand; two of two do not.
- **Approve** retires exactly the set waiting — the provider refuses any other set, so a person approves
what they were shown.
- **Reject** keeps the set active; the provider does not ask about it again while the set stays the same.
The rejection is held by the provider's process: a restart asks again, loudly. A rejected set can still
be approved later.
- **Settled**: a waiting or rejected set ends without retiring when the mesh asks for any of it again or
the set changes; a new stable set starts the count over.
**5. The backend remembers, not the process.** Each provider lists from its own backend what the mesh
made, active and retired, with the mark. So a restart forgets nothing; a consumer the backend holds
active and the mesh no longer asks for is retired by the same rules after a restart as before one; and a
consumer **found disabled without the mark** — withdrawn before this record — is **adopted** as retired:
marked, said, announced, its clock starting then. What the mesh made is known by the mark, or, before
the mark existed, by its shape (postgres: a role valid until *infinity*, which only the
mesh's role statement sets, owning a database of its own name; keycloak: the
`mesh.provisioned` attribute it already had). Anything else is somebody else's and is never listed,
retired or deleted.
**6. Cleanup is the controller's verbs, executed by the provider.**
- `cleanup list` — every retired consumer per provider: its age, its size where the backend can say,
and why.
- `cleanup delete <node> <provider-module> <consumer> --why` — one.
- `cleanup delete --older-than <days> --why` — lists what it would delete; deletes only with the
operator's explicit `--confirm`.
- **The provider deletes**, because it owns its backend: the controller asks the provider's tool on that
machine through the mesh, as any module's tool is asked, and never touches a backend itself. A provider
refuses to delete anything active or asked for, and anything not retired.
- **Every deletion is written to the hand-act log**, as is every approval and rejection — including one
made by asking a provider's tool directly rather than through the controller.
**7. Every provider serves the same four tools**, the protocol between the verbs and the providers:
`provisioner_retirement` (what is held, waiting, rejected and retired), `provisioner_retire_approve`,
`provisioner_retire_reject` and `provisioner_delete`, each act requiring a why. And says one event,
`provisioner.retirement`, whose `change` is `waiting`, `settled`, `approved`, `rejected`, `retired`,
`reenabled`, `deleted` or `adopted`; the controller derives the permission to publish it for every module
that receives contributions, as ADR 0224 does the standing events.
**8. Nothing is silent.** Retire, approve, reject, re-enable and delete are each announced and said in the
provider's journal; waiting is an urgent condition and a rejection a warning one, both naming the
provider and its machine; and a self-check probe raises a **low-severity `cleanup-waiting` condition**
for any provider holding something retired **more than thirty days**.
**9. The withdrawal brake paragraph of ADR 0229 is replaced by this record.** The rest of 0229 — the
lease, the epoch, the report's order, one writer at composition, the plan by compare-and-set, the lint,
every consumed kind's contract — stands unchanged.
## Consequences
- **The first rollout surfaces what is already there.** On the home server the consumer locked by an
earlier withdrawal is adopted as retired; the control node's six set-aside databases are listed, and
raise `cleanup-waiting` thirty days after their date; any consumer a backend holds open that the mesh no
longer asks for becomes a retirement — waiting for a person if there are more than three. Nothing is
deleted by the rollout.
- **Retired data is still backed up and still takes space** until a person deletes it. That is the
point; the thirty-day condition is what keeps it from being forgotten.
- **A person who rejects must come back.** A rejected set is kept active and said as a warning until the
mesh asks for it again or someone approves it.
- **The TypeScript providers get the stable count and the threshold** from the SDK's loop on their next
build (the SDK's provisioner, every catalogue provider's range accepts it), but a set over the bound
there waits with no way to approve it until each provider passes its module name and an announcer to
the loop, and they cannot list or delete what they retired until their adapters can. Until then they
are covered for safety and not for cleanup, and the record says so rather than claiming otherwise.
- **The Go loop is still two identical copies**, in postgres and keycloak,
held together by a test — now over three files. Its home is the Go SDK ([ADR 0039](0039-what-the-sdk-holds-and-refuses.md));
moving it there means a tagged SDK release before the catalogue can use it, a separate change.
- **The rollout order**: the controller first (it derives the permission to publish the new event and
hears it; an older controller makes the provider's announcement a refusal the provider logs), then the
catalogue's two Go providers, then the SDK's release and each TypeScript provider as it is rebuilt.
## How it is checked
| What | Checked by |
|---|---|
| a transient empty list for four passes retires nothing; five stable passes retire; an unreadable pass and a changed set restart the count; additions and changes are not delayed | the catalogue's shared `retirement_test.go` (identical in both Go providers) and the SDK's `retire.test.ts` |
| over the bound waits, is announced and said, again every fifteen minutes; approve takes only the exact set; reject keeps it and is not asked again; a set asked for again settles | the same tests |
| retired is disabled with data intact; asked for again it is enabled as it was | postgres's `live_test.go` and keycloak's `live_retire_test.go`, against throwaway servers |
| delete removes only that consumer, never an active or asked one | the same live tests, and `retirement_test.go` |
| a restart retires what the backend holds unasked and adopts what it finds disabled | `retirement_test.go`, the provider's inventory tests |
| the conditions, verbs, hand acts and the thirty-day probe | the controller's tests of the event, the verbs against a fake provider on a real bus, and the probe |
| the two Go copies agree | `harness_same_test.go` in each provider, over the three files |
| live, after rollout | `cleanup list` names the adopted and set-aside entries; `conditions` shows no `retire-waiting` on a mesh nobody is changing |
## References
- [ADR 0229](0229-the-cores-order-is-a-lease-the-store-remembers-and-an-epoch-a-machine-is-sent-once-it-reads-one.md)
— replaced in part: its withdrawal brake paragraph. Everything else in it stands.
- [ADR 0227](0227-the-core-holds-nine-rules-each-checked-and-is-built-to-them-in-six-phases.md) rule 4,
*nothing dropped silently* — which this keeps: a stable result over the bound stops and raises a
condition.
- [ADR 0224](0224-a-provider-that-keeps-failing-a-consumer-is-a-problem-the-controller-reports.md)
— the provider's events and their derived permission, which the retirement event follows.
- [Issue 241](../04-ISSUES/241-one-unreadable-grants-file-dropped-every-database-on-the-control-node/00-report.md)
— what withdrawal destroyed, and what it stopped destroying.
- [To-be 45](../03-DESIGN/01-to-be/45-a-core-that-cannot-fail-silently.md) §2, §4, §7 and Phase 2, and
[the module protocol](../03-DESIGN/01-to-be/19-the-module-protocol.md), amended alongside.
- mesh-catalog `modules/postgres` and `modules/keycloak` (`retirement.go`, `retire_pg.go`, `oidc.go`);
mesh-sdk `src/provisioner` 0.1.12; mesh-controller's `retire` and `cleanup` verbs and its probe.
+1
View File
@@ -201,6 +201,7 @@ python3 00-META/checks/index.py fail if stale
- **0224** — [A provider that keeps failing a consumer is a problem the controller reports](0224-a-provider-that-keeps-failing-a-consumer-is-a-problem-the-controller-reports.md)
- **0227** — [The core holds nine rules, each checked, and is built to them in six phases](0227-the-core-holds-nine-rules-each-checked-and-is-built-to-them-in-six-phases.md)
- **0229** — [The core's order is a lease the store remembers, and an epoch a machine is sent once it reads one](0229-the-cores-order-is-a-lease-the-store-remembers-and-an-epoch-a-machine-is-sent-once-it-reads-one.md)
- **0230** — [A consumer the mesh stops asking for is retired, and deleted only by a person](0230-a-consumer-the-mesh-stops-asking-for-is-retired-and-deleted-only-by-a-person.md)
- **0231** — [A healer acts on what observation raised, and only observation says it worked](0231-a-healer-acts-on-what-observation-raised-and-only-observation-says-it-worked.md)
### Its tiers, from the bottom up
@@ -10,6 +10,7 @@ code:
updated: 2026-10-06
decisions:
- 02-DECISIONS/0224-a-provider-that-keeps-failing-a-consumer-is-a-problem-the-controller-reports.md
- 02-DECISIONS/0230-a-consumer-the-mesh-stops-asking-for-is-retired-and-deleted-only-by-a-person.md
- 02-DECISIONS/0095-the-control-plane-is-the-way-to-ask-a-module.md
- 02-DECISIONS/0106-the-bus-is-nats.md
- 02-DECISIONS/0128-the-mesh-bus-is-required-not-ambient.md
@@ -246,6 +247,19 @@ receives contributions may publish both, derived and never declared. The control
failing word per provider, machine and consumer, and `status` names each one until it recovers
([ADR 0224](../../02-DECISIONS/0224-a-provider-that-keeps-failing-a-consumer-is-a-problem-the-controller-reports.md)).
### A consumer the mesh stops asking for is retired
A consumer is active, retired or deleted
([ADR 0230](../../02-DECISIONS/0230-a-consumer-the-mesh-stops-asking-for-is-retired-and-deleted-only-by-a-person.md)).
The provider's loop retires one only after the same set has gone unasked in five consecutive passes
that read the contributions file; retiring disables its access reversibly and marks it, in the
provider's own backend, with when and why; asked for again, the ordinary create re-enables it. A set of
more than three, or of more than half of those held, waits for a person. Every provider serves four
tools for this — `provisioner_retirement`, `provisioner_retire_approve`, `provisioner_retire_reject`,
`provisioner_delete` — and says `provisioner.retirement`, permitted like the two events above. The
controller's `retire` and `cleanup` verbs ask those tools on the provider's machine; nothing else deletes
a consumer.
### Checked, and it agrees (2026-09-16)
This looked like the sharpest disagreement and was not one. The live wire is the contributions file
@@ -1,12 +1,13 @@
---
layer: to-be
status: in-progress
code: [mesh-controller, mesh-host, mesh-tools, mesh-catalog, mesh-lab]
code: [mesh-controller, mesh-host, mesh-tools, mesh-catalog, mesh-sdk, mesh-lab]
updated: 2026-10-06
decisions:
- 02-DECISIONS/0227-the-core-holds-nine-rules-each-checked-and-is-built-to-them-in-six-phases.md
- 02-DECISIONS/0229-the-cores-order-is-a-lease-the-store-remembers-and-an-epoch-a-machine-is-sent-once-it-reads-one.md
- 02-DECISIONS/0231-a-healer-acts-on-what-observation-raised-and-only-observation-says-it-worked.md
- 02-DECISIONS/0230-a-consumer-the-mesh-stops-asking-for-is-retired-and-deleted-only-by-a-person.md
- 02-DECISIONS/0224-a-provider-that-keeps-failing-a-consumer-is-a-problem-the-controller-reports.md
- 02-DECISIONS/0218-a-plan-sends-grants-before-code-rolls-out-one-machine-first-and-a-newer-merge-takes-over-an-older-plan.md
- 02-DECISIONS/0141-the-host-delivers-its-own-successor.md
@@ -45,8 +46,9 @@ Owning repositories: **mesh-controller** (the condition store, watchdogs, `docto
calls, the hand-act log, the facts snapshot), **mesh-host** (the node-engine: the apply queue, report
order, epoch refusal, the `report` verb, rollback witnessing), **mesh-tools** (the node tools and the
console: their heartbeat, passing every argument, health answers), **mesh-catalog** (the
`operator-channel` seat's holder and channels, the watcher, the provider brake, the catalogue's merge
gate), **mesh-lab** (the replays and the induced-failure scenarios).
`operator-channel` seat's holder and channels, the watcher, the providers' retirement, the catalogue's
merge gate), **mesh-sdk** (the TypeScript providers' loop and its retirement), **mesh-lab** (the
replays and the induced-failure scenarios).
---
@@ -68,6 +70,7 @@ Every kind of state the core keeps, and the one component that writes it. Anyone
| builds and their outcomes | the build seat's holder | its own state | the controller asks |
| a merge announced | **one** announcer per forge (the hook, or the poll when the hook is absent — never both) | the bus | — |
| a provider's standing | the provider | the provider's events | the controller keeps the newest word as a condition |
| a consumer's retirement: active, retired (when, why), deleted | the provider | its own backend's mark; said on `provisioner.retirement` | the controller asks the provider's tools; a person approves, rejects and deletes through the controller's verbs |
| the operator-channel's open messages | the seat's holder | its own key-value state | — |
| the facts snapshot | controller | the artifact store, `facts/latest` | the build seat reads |
@@ -145,6 +148,13 @@ outlive the history.
with their expiry. The all-well sentence requires none open, silenced included.
- **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))
raises three, keyed by the provider module and its machine: `retire-waiting` (urgent) while a stable
set too large to retire alone waits for `retire approve` or `retire reject`, listing it; `retire-rejected`
(warning) while a rejected set is kept active though the mesh no longer asks for it; and
`cleanup-waiting` (warning), from the probe D11, while anything retired is older than thirty days. The
first two clear on the provider's `retired`, `settled` or `approved` word, and all three when the
provider is no longer assigned there.
## 3. The signals table (rule 5)
@@ -196,6 +206,7 @@ itself — an unanswered probe is never a pass.
| D8 | no address the mesh owns is in a ban list | 238 |
| D9 | `status` answers in full within ten seconds | 265 |
| D10 | every machine runs the node-engine and node tools builds its plan says, or is inside a plan's window | version split |
| D11 | no provider holds a consumer retired more than thirty days: each provider assigned is asked `provisioner_retirement` on its machine (ADR 0230); one that does not serve it yet is named, not failed | ADR 0230 |
| DW | the watchdogs of §3 ran within three of their intervals: the watchers are watched, and raise S10 when the self-check stops | rule 6 |
| H-* | the health probes of §8, run for every core component on every machine | rule 8 |
@@ -331,7 +342,9 @@ all healers together, stop every healer — `mesh.healers.braked`, urgent — un
A heal is never a hand act. `healers` lists the registry, the acts and the brake.
**The hand-act log.** Every verb that repairs by hand — a named `push` outside a plan, `plans close`,
`broker consumer-reset`, `conditions silence`, and `hand-act record` for an act done outside the mesh —
`broker consumer-reset`, `conditions silence`, `retire approve`, `retire reject` and `cleanup delete`
(ADR 0230; and an approval, rejection or deletion a provider reports that did not come through the
controller), and `hand-act record` for an act done outside the mesh —
takes a required `--why` and writes an entry: who, which verb and arguments, why, when, the condition
key it addresses if any, and a **cause** (the condition kind, or a word the person gives). `status`
shows the week's count. A cause recorded twice within fourteen days raises `healer-wanted` (S15).
@@ -396,7 +409,7 @@ outcome, run on every merge to mesh-controller, mesh-host and mesh-tools:
| R3 | the node-engine self-updates during its report (230, 264) | the report arrives under the new build |
| R4 | the bus's authorization reloads during a call (265) | the call's outcome is readable by id |
| R5 | a consumer with several filter subjects under mixed traffic (266) | no announcement is skipped; S5 fires if one is |
| R6 | an unreadable contributions file (241) | refused by name; nothing withdrawn; a condition |
| R6 | an unreadable contributions file (241) | refused by name; nothing retired; a condition |
| R7 | the controller rebuilds itself mid-plan (214) | the plan continues under the new epoch |
| R8 | a broken controller, node-engine and node tools build | each rolled back with no hand; condition and message |
| R9 | each signal of §3 suppressed | its condition within its bound, cleared on return |
@@ -447,11 +460,11 @@ bound corrected in the table.
| mesh-controller | the lease and epoch (§6); plans written by compare-and-set; a report kept by sequence; abandoned calls marked; S12, S13, D5; the writers table enforced at grant composition; contract tests for every consumed subject and the check listing them; the empty-on-error lint |
| mesh-host | one apply queue; the report sequence kept on disk; epoch refusal reported; the `report` verb; the contract tests and lint |
| mesh-tools | refusing an unreadable or unknown input by name; the lint |
| mesh-catalog | the withdrawal brake in the providers' loop: a reconcile that would withdraw more than one consumer, or a set fraction, stops and raises a condition |
| mesh-catalog, mesh-sdk | retirement in the providers' loop (ADR 0230, replacing ADR 0229's brake): a consumer no longer asked for is retired — disabled, marked, its data kept — only once the same result holds for five passes; a set of more than three, or more than half of those held, waits for a person and raises a condition; the four retirement tools on every provider |
| mesh-lab | R1, R2, R6 |
**Done when:** R1 ends with nothing from the stale epoch applied, R2 with one report of the newest
sequence, R6 with the withdrawal braked; every consumed subject has its contract test.
sequence, R6 with nothing retired; every consumed subject has its contract test.
**Built 2026-10-06** ([ADR 0229](../../02-DECISIONS/0229-the-cores-order-is-a-lease-the-store-remembers-and-an-epoch-a-machine-is-sent-once-it-reads-one.md)):
the controller's lease and epoch, plans by compare-and-set, accounts kept by order, S12, S13 by writer,
@@ -461,6 +474,15 @@ providers' loop and the SDK's. **Not yet:** the replays R1, R2 and R6 in mesh-la
and lint; the lint in mesh-host; the SDK's loop announcing a provider's standing, without which its
brake is said in its journal only.
**Amended 2026-10-06** ([ADR 0230](../../02-DECISIONS/0230-a-consumer-the-mesh-stops-asking-for-is-retired-and-deleted-only-by-a-person.md),
the operator's decision): the brake's hourly release is replaced by retirement — three states (active,
retired, deleted), five stable passes before a consumer is retired, a person for more than three or more
than half, `retire approve|reject`, `cleanup list|delete`, `retirement` events and D11. Built on branches
in mesh-catalog (both Go providers), mesh-sdk (0.1.12) and mesh-controller, not yet merged. **Not yet:**
the TypeScript providers passing their module name and an announcer to the loop, without which a set
over the bound there waits with no way to approve it, and adapters that list and delete what they
retired.
### Phase 3 — Healers
| Repository | Delivers |