postgres, keycloak: retire a consumer, delete only on a person's word (hq ADR 0230) #91

Merged
mesh-admin merged 2 commits from feat/retired-consumers into main 2026-10-06 12:51:35 +00:00
Contributor

Replaces the hourly-release withdrawal brake (#90) in the shared Go provider loop, used by postgres and keycloak.

Loop (retirement.go, shared by both providers):

  • Retires only after the same unasked set has held for 5 consecutive readable passes. An unreadable pass or a changed set resets the count.
  • More than 3, or more than half of those held (when more than one is held), waits for a person. The provider announces provisioner.retirement waiting and repeats it every 15 min.
  • Approve accepts only the exact set waiting. Reject keeps the set active until it changes. When the mesh asks for a consumer again, the set settles.
  • A retired consumer the mesh asks for again is re-enabled by the ordinary create, and reenabled is announced.
  • At start the provider reads its backend's inventory: active orphans go through the same rules, and consumers found disabled without a mark are adopted as retired.
  • Four tools on each provider: provisioner_retirement, provisioner_retire_approve, provisioner_retire_reject, provisioner_delete.
  • harness_same_test now holds harness.go, retirement.go and retirement_test.go identical in both providers.

postgres: retired means

  • ALTER ROLE NOLOGIN and its sessions are terminated.
  • The role comment gets {"mesh":"consumer","node":…,"retired":…,"why":…}.
  • The database is untouched: CONNECT is not revoked and allow_connections stays on, so backups still dump it.
  • Mesh-made is recognised by the mark, or by shape: VALID UNTIL infinity and owning a same-named database.
  • <name>_deleted_<date> databases are listed as set-aside.
  • Delete drops the DB, then the role, unless the role still owns another DB.

keycloak: retired means

  • The client gets enabled:false plus attributes mesh.retired and mesh.retired-why.
  • Secret, redirects and mappers are kept. Ensure re-enables the client and strips the marks.
  • Delete removes the client only if it is mesh-made and disabled.
  • Before this, withdrawal DELETEd the client.

Tests:

  • Unit:
    • postgres 52 pass, keycloak suite all pass.
    • A transient empty file for 4 passes retires nothing; 5 passes retire.
    • Re-enable works.
    • Over the threshold: waits; approve and reject work; settles when asked again.
    • Delete removes only that consumer.
    • Restart and adoption.
    • Bound arithmetic.
    • Tools.
  • Live, against throwaway pgvector:pg17 and keycloak 26.0.8, both removed afterwards:
    • A retired login is refused, its data is intact and it is listed retired. It comes back with its data. Delete drops only its own. A set-aside DB is listed and deleted.
    • A retired client is refused at /auth and keeps its secret. Re-enabled, it is served again. It is never deleted while enabled.

Rollout: after mesh-controller feat/retired-consumers is live.

Expect on first start:

  • ace's postgres adopts mesh_ace_letta (NOLOGIN, not asked) as retired.
  • novox's postgres lists 6 *_deleted_20261005 databases. They raise cleanup-waiting from 2026-11-04.
  • No deletions.
Replaces the hourly-release withdrawal brake (#90) in the shared Go provider loop, used by postgres and keycloak. **Loop** (`retirement.go`, shared by both providers): - Retires only after the same unasked set has held for 5 consecutive readable passes. An unreadable pass or a changed set resets the count. - More than 3, or more than half of those held (when more than one is held), waits for a person. The provider announces `provisioner.retirement` `waiting` and repeats it every 15 min. - Approve accepts only the exact set waiting. Reject keeps the set active until it changes. When the mesh asks for a consumer again, the set settles. - A retired consumer the mesh asks for again is re-enabled by the ordinary create, and `reenabled` is announced. - At start the provider reads its backend's inventory: active orphans go through the same rules, and consumers found disabled without a mark are adopted as retired. - Four tools on each provider: `provisioner_retirement`, `provisioner_retire_approve`, `provisioner_retire_reject`, `provisioner_delete`. - `harness_same_test` now holds harness.go, retirement.go and retirement_test.go identical in both providers. **postgres: retired means** - `ALTER ROLE NOLOGIN` and its sessions are terminated. - The role comment gets `{"mesh":"consumer","node":…,"retired":…,"why":…}`. - The database is untouched: CONNECT is not revoked and allow_connections stays on, so backups still dump it. - Mesh-made is recognised by the mark, or by shape: VALID UNTIL infinity and owning a same-named database. - `<name>_deleted_<date>` databases are listed as set-aside. - Delete drops the DB, then the role, unless the role still owns another DB. **keycloak: retired means** - The client gets `enabled:false` plus attributes `mesh.retired` and `mesh.retired-why`. - Secret, redirects and mappers are kept. Ensure re-enables the client and strips the marks. - Delete removes the client only if it is mesh-made and disabled. - Before this, withdrawal DELETEd the client. **Tests:** - Unit: - postgres 52 pass, keycloak suite all pass. - A transient empty file for 4 passes retires nothing; 5 passes retire. - Re-enable works. - Over the threshold: waits; approve and reject work; settles when asked again. - Delete removes only that consumer. - Restart and adoption. - Bound arithmetic. - Tools. - Live, against throwaway pgvector:pg17 and keycloak 26.0.8, both removed afterwards: - A retired login is refused, its data is intact and it is listed retired. It comes back with its data. Delete drops only its own. A set-aside DB is listed and deleted. - A retired client is refused at /auth and keeps its secret. Re-enabled, it is served again. It is never deleted while enabled. **Rollout:** after mesh-controller feat/retired-consumers is live. Expect on first start: - ace's postgres adopts mesh_ace_letta (NOLOGIN, not asked) as retired. - novox's postgres lists 6 `*_deleted_20261005` databases. They raise cleanup-waiting from 2026-11-04. - No deletions.
mesh-admin added 1 commit 2026-10-06 12:25:41 +00:00
The hourly release of ADR 0229's brake still ended in the mesh acting alone on
a mistake. A consumer now stays active until the same unasked set holds for
five passes, waits for a person past three or half of those held, is disabled
and marked rather than withdrawn, comes back as it was when asked again, and is
deleted only through the provider's delete tool. The backend keeps the mark, so
a restart forgets nothing and finds what was withdrawn before.
jschoubben added 1 commit 2026-10-06 12:35:52 +00:00
Five passes are twenty-five seconds, shorter than a controller restart, a store
reconnecting or a file half written; the operator asked for both (hq ADR 0230).
Author
Contributor

Updated in 2c15074.

Change: a consumer is retired only once the same unasked set has held both for 5 consecutive readable passes and for StableFor = 10 min since the first of those passes. The constant is in retirement.go. An unreadable pass or a changed set restarts both. Creates and rotations still act on the first pass.

New test: TestFivePassesInTwentyFiveSecondsRetireNothingTenMinutesDo:

  • 5 passes in 25 s retire nothing;
  • the same set held for 9.5 minutes retires nothing;
  • the same set held past 10 minutes is retired;
  • 10 minutes covered by 2 slow passes is not enough until 5 passes have run.

The other tests were moved to the 10-minute clock.

Suites rerun: unit tests pass for postgres and keycloak. Live tests also pass against throwaway pgvector:pg17 and keycloak 26.0.8, both removed afterwards. The READMEs say "five passes and ten minutes".

Updated in 2c15074. **Change:** a consumer is retired only once the same unasked set has held **both** for 5 consecutive readable passes **and** for `StableFor` = 10 min since the first of those passes. The constant is in `retirement.go`. An unreadable pass or a changed set restarts both. Creates and rotations still act on the first pass. **New test:** `TestFivePassesInTwentyFiveSecondsRetireNothingTenMinutesDo`: - 5 passes in 25 s retire nothing; - the same set held for 9.5 minutes retires nothing; - the same set held past 10 minutes is retired; - 10 minutes covered by 2 slow passes is not enough until 5 passes have run. The other tests were moved to the 10-minute clock. Suites rerun: unit tests pass for postgres and keycloak. Live tests also pass against throwaway pgvector:pg17 and keycloak 26.0.8, both removed afterwards. The READMEs say "five passes and ten minutes".
mesh-admin merged commit 368fa2f45e into main 2026-10-06 12:51:35 +00:00
mesh-admin deleted branch feat/retired-consumers 2026-10-06 12:51:35 +00:00
Author
Contributor

Delivery novox/mesh-catalog@368fa2f45e9b — held since 2026-10-06T23:05:28Z

Held for a person: merged without a passing check: only a person decides that it goes on — mesh-delivery.release with why lets it go on.

Delivery plan — builds keycloak, postgres, sent nowhere; no bus step; 3 wait(s) for a person

  • tier 0: keycloak, postgres
  • ace: waits for a person: postgres
  • novox: waits for a person: keycloak, postgres
  • keycloak waits for a person: its policy records (module)
  • keycloak provides oidc-client: its consumers are sent again after it
  • keycloak keeps data (ADR 0233): what it holds is backed up before it moves
  • postgres waits for a person: its policy records (module)
  • postgres provides postgres-database: its consumers are sent again after it
  • postgres keeps data (ADR 0233): what it holds is backed up before it moves

Transitions

  • 2026-10-06 23:05 (new) → proposed (announced): novox/mesh-catalog#91 merged; its head was not heard by this owner, so it is made from the forge's word
  • 2026-10-06 23:05 proposed → held (merged-unchecked): merged without a passing check — a verdict known with it is taken first: only a person decides that it goes on

The commit's note under refs/notes/mesh-plan keeps every transition: git log --notes=mesh-plan.

<!-- mesh-delivery:view --> **Delivery** `novox/mesh-catalog@368fa2f45e9b` — **held** since 2026-10-06T23:05:28Z Held for a person: merged without a passing check: only a person decides that it goes on — `mesh-delivery.release` with why lets it go on. **Delivery plan** — builds keycloak, postgres, sent nowhere; no bus step; 3 wait(s) for a person - tier 0: keycloak, postgres - ace: waits for a person: postgres - novox: waits for a person: keycloak, postgres - keycloak waits for a person: its policy records (module) - keycloak provides oidc-client: its consumers are sent again after it - keycloak keeps data (ADR 0233): what it holds is backed up before it moves - postgres waits for a person: its policy records (module) - postgres provides postgres-database: its consumers are sent again after it - postgres keeps data (ADR 0233): what it holds is backed up before it moves **Transitions** - 2026-10-06 23:05 (new) → proposed (announced): novox/mesh-catalog#91 merged; its head was not heard by this owner, so it is made from the forge's word - 2026-10-06 23:05 proposed → held (merged-unchecked): merged without a passing check — a verdict known with it is taken first: only a person decides that it goes on The commit's note under `refs/notes/mesh-plan` keeps every transition: `git log --notes=mesh-plan`.
Sign in to join this conversation.
No Reviewers
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: novox/mesh-catalog#91