From 7ba39c589db3bf017e08ddb593e6e25b40a84501 Mon Sep 17 00:00:00 2001 From: Ryan Born Date: Wed, 23 Sep 2026 12:44:13 -0400 Subject: [PATCH 1/8] =?UTF-8?q?Provider=20accounts=20P12:=20docs=20+=20mod?= =?UTF-8?q?uledocs=20=E2=80=94=20retire=20the=20"quota=20is=20per=20worksp?= =?UTF-8?q?ace"=20mental=20model?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Update `docs/provider-account-design.md` Status line from "design proposal" to "implemented" (P0–P11 all shipped as of 2026-09-23) - Remove "until P7" reference from `Arbiter.Quota.serialize/3` docstring since P7 has shipped - Rename CloudProbe section heading from "What a refresh does per workspace" to "Refresh strategy" to reflect account-focused Anthropic polling while keeping per-workspace fanning for other providers - Update `arb quota` moduledoc to say snapshots are stored "per account" instead of "per workspace" - Update workspace concurrency config UI to mention the account ceiling (P8) as part of the effective cap formula Co-Authored-By: Claude Haiku 4.5 --- apps/arbiter/lib/arbiter/quota.ex | 6 ++---- apps/arbiter/lib/arbiter/quota/cloud_probe.ex | 2 +- apps/arbiter_cli/lib/arbiter_cli/cmd/quota.ex | 5 +++-- .../live/workspace_detail/policy_config_component.ex | 2 +- docs/provider-account-design.md | 3 +-- 5 files changed, 8 insertions(+), 10 deletions(-) diff --git a/apps/arbiter/lib/arbiter/quota.ex b/apps/arbiter/lib/arbiter/quota.ex index 2142c184..0472e959 100644 --- a/apps/arbiter/lib/arbiter/quota.ex +++ b/apps/arbiter/lib/arbiter/quota.ex @@ -384,10 +384,8 @@ defmodule Arbiter.Quota do shape (string-friendly, ISO-8601 timestamps), or `nil` when none exists. `:workspace_id` names the workspace whose gate config annotates the - `gating_*` fields. Thresholds are still workspace-scoped until P7, so a - caller that came in through `arb quota --workspace X` passes X here and - gets the same answer it did before the re-key; with none given the - account's alphabetically-first workspace stands in. + `gating_*` fields (for backward compatibility with `arb quota --workspace`). + When none is given, the account's alphabetically-first workspace stands in. `:spend_cache` optionally supplies a `spend_cache/1` memo so a caller that also lists the other providers pays for the ledger scan once — see diff --git a/apps/arbiter/lib/arbiter/quota/cloud_probe.ex b/apps/arbiter/lib/arbiter/quota/cloud_probe.ex index 6ac54e70..a483c903 100644 --- a/apps/arbiter/lib/arbiter/quota/cloud_probe.ex +++ b/apps/arbiter/lib/arbiter/quota/cloud_probe.ex @@ -21,7 +21,7 @@ defmodule Arbiter.Quota.CloudProbe do quota tables, could never show them, and there was no history to audit. This GenServer closes that gap. - ## What a refresh does per workspace + ## Refresh strategy * `Arbiter.Quota.Codex.fetch/2` — one GET to OpenAI's usage endpoint using the `codex` CLI's stored token; upserts `CodexQuota` + broadcasts. diff --git a/apps/arbiter_cli/lib/arbiter_cli/cmd/quota.ex b/apps/arbiter_cli/lib/arbiter_cli/cmd/quota.ex index b5c49a46..41dbba24 100644 --- a/apps/arbiter_cli/lib/arbiter_cli/cmd/quota.ex +++ b/apps/arbiter_cli/lib/arbiter_cli/cmd/quota.ex @@ -8,8 +8,9 @@ defmodule ArbiterCli.Cmd.Quota do * Claude: OAuth polling of Anthropic's `/api/oauth/usage` endpoint plus `anthropic-ratelimit-unified-*` headers captured from worker responses. - Stores the latest snapshot per workspace, including per-model weekly - breakdown and `extra_usage` overage (bd-8tpha6, bd-b0zody). + Stores the latest snapshot per account (P5, `docs/provider-account-design.md` + §6), including per-model weekly breakdown and `extra_usage` overage + (bd-8tpha6, bd-b0zody). * Codex: OpenAI session + weekly windows, refreshed by the quota probe using the `codex` CLI's stored token. Shows a short message until a snapshot has been captured (i.e. the CLI isn't authenticated on this host). diff --git a/apps/arbiter_web/lib/arbiter_web/live/workspace_detail/policy_config_component.ex b/apps/arbiter_web/lib/arbiter_web/live/workspace_detail/policy_config_component.ex index a4b8adb2..aaec981c 100644 --- a/apps/arbiter_web/lib/arbiter_web/live/workspace_detail/policy_config_component.ex +++ b/apps/arbiter_web/lib/arbiter_web/live/workspace_detail/policy_config_component.ex @@ -780,7 +780,7 @@ defmodule ArbiterWeb.WorkspaceDetail.PolicyConfigComponent do <.setting_row name="Max concurrent workers" - consequence="conductor.max_concurrent — per workspace; the effective cap is the lowest of this, the system cap and quota headroom" + consequence="conductor.max_concurrent — the effective cap is the lowest of this, the account ceiling (P8, if configured), the system cap and quota headroom (P7)" > <:control> Date: Wed, 23 Sep 2026 12:50:30 -0400 Subject: [PATCH 2/8] P12 review fixes: P4 deviation note and account-model README section - Update design doc Status line to note account model is opt-in (`:provider_accounts_enabled` defaults false) - Add "What P4 actually shipped" section documenting the deferred destructive step (flagged off per #1947 reversal commit 7eaaaafe) - Add account-model path subsection to README explaining `arb account attach` / `arb account rotate` as alternative to install-wide token setup (requires `:provider_accounts_enabled`) - Clarify precedence and use-case for account-scoped credentials Addresses reviewer findings from P12 acceptance criteria: - Verifies Status line reflects actual implementation state - Documents the one significant design deviation (P4 deferral) - Provides operators the account-model onboarding docs named in task --- README.md | 38 +++++++++++++++++++++++++++------ docs/provider-account-design.md | 22 ++++++++++++++++++- 2 files changed, 52 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index b71b6fe6..16e8a37a 100644 --- a/README.md +++ b/README.md @@ -397,14 +397,38 @@ copy is rotated and the others aren't. installing shell into `~/.arbiter/arbiter.env` automatically, same as the other captured secrets above. +#### Account-model path (requires `:provider_accounts_enabled`) + +If your install has enabled the provider-account model (see +`docs/provider-account-design.md` and `config/config.exs:131`), you can +instead attach workspace credentials to named provider accounts via the `arb +account` CLI: + +```sh +# Create or reference a provider account +arb account create claude my_account + +# Attach it to a workspace +arb account attach claude my_account + +# Install or rotate the credential +arb account rotate claude --token +``` + +This path is particularly useful if you have **multiple Claude credentials** +(e.g., for different Anthropic accounts or organizations) and want to route +different workspaces to different accounts — the account model lets each +workspace reference its own account identity directly, without duplicating +tokens across workspaces or relying on install-wide environment fallbacks. + **Precedence when both are set:** a spawn can end up with both -`CLAUDE_CODE_OAUTH_TOKEN` (install-wide) and `ANTHROPIC_API_KEY` (workspace -`credentials_ref`/`api_keys` rotation) in its environment at once. Which one -the `claude` CLI honours is decided by the CLI itself, not by Arbiter — if it -prefers the OAuth token, a workspace that deliberately configured its own key -would silently authenticate against the install-wide account instead. If a -workspace's `ANTHROPIC_API_KEY` must win, verify the CLI's actual precedence -before relying on it, or unset the install-wide token for that install. +`CLAUDE_CODE_OAUTH_TOKEN` (install-wide) and credentials from the account model +in its environment at once. Which one the `claude` CLI honours is decided by +the CLI itself, not by Arbiter — if it prefers the OAuth token, a workspace +with an account-attached credential would silently authenticate against the +install-wide account instead. If a workspace's account credential must win, +verify the CLI's actual precedence before relying on it, or unset the +install-wide token for that install. **Redaction:** `Arbiter.Worker.ClaudeSession.start/1` adds `CLAUDE_CODE_OAUTH_TOKEN`/`ANTHROPIC_API_KEY` values to the session's diff --git a/docs/provider-account-design.md b/docs/provider-account-design.md index 1093d5e2..583fd2a6 100644 --- a/docs/provider-account-design.md +++ b/docs/provider-account-design.md @@ -1,6 +1,6 @@ # Provider accounts — extracting credentials, quota and cost out of the workspace -**Status:** implemented (P0–P11 all shipped as of 2026-09-23). No significant deviations from the design. +**Status:** implemented behind `:provider_accounts_enabled` (default off) as of 2026-09-23; P0–P11 all shipped. See §7.5 for deviations from the original design. **Date:** 2026-09-12 **Task:** bd-7df8nh · **Tracker:** github:1593 **Author:** worker @@ -637,6 +637,26 @@ false. Two decisions the rows above do not spell out: the unambiguous install-wide *account* credential with those steps beneath it as a floor. P4 deletes them; P3 does not. +**What P4 actually shipped (bd-6yb06i).** The destructive step — removal of +the legacy `ConfigDir` fallback chain and deletion of install-wide-unambiguous +sources — was deferred per operator ruling on #1947 (commit `7eaaaafe`). The +design intended P4 to land as the final step of a three-phase sequence +(N / N+1 / N+2 in §7.5), making the account model mandatory once the rollback +window closed. Instead, the fallback chain remains in place: `ConfigDir.oauth_token/1` +still consults the server process env and `CLAUDE_CODE_OAUTH_TOKEN` as fallbacks +when no account is found. This means: + +* **The account model is opt-in, not the live default.** `:provider_accounts_enabled` + defaults `false` in every environment (§7.5 / `config/config.exs:131`), and P3's + read paths are gated on it. A workspace without an explicit account join still + sources credentials from its `encrypted_worker_env` blob, and the fleet-wide + probes still fall back to install-wide sources. The reversal preserves the + three-release N / N+1 / N+2 structure. +* **No further destructive changes.** P4's nominal scope (deletion) is closed. + Extending the fallback chain or changing its behaviour is a future decision + and requires explicit work, not a phase; there is no "P4 rolled back but P12 + deletes it anyway" trap. + ### 7.6 `ARBITER_CLOAK_KEY` rotation: **keep it separate, and do it first** The key is considered exposed (printed into a transcript, 2026-09-12). The From e400de8b45aeb92cf7fb7881002bd4c6ee9ad874 Mon Sep 17 00:00:00 2001 From: Ryan Born Date: Wed, 23 Sep 2026 12:52:12 -0400 Subject: [PATCH 3/8] Fix arb account rotate CLI syntax in README example The example was showing an incorrect command line: arb account rotate claude --token This doesn't match the real CLI signature which is: arb account rotate --kind oauth_token|api_key|cli_credentials_file --env-var VAR (--secret VALUE | --secret-file PATH | -) The correct example is: arb account rotate claude:my_account --kind oauth_token --env-var CLAUDE_CODE_OAUTH_TOKEN --secret --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 16e8a37a..8154f154 100644 --- a/README.md +++ b/README.md @@ -412,7 +412,7 @@ arb account create claude my_account arb account attach claude my_account # Install or rotate the credential -arb account rotate claude --token +arb account rotate claude:my_account --kind oauth_token --env-var CLAUDE_CODE_OAUTH_TOKEN --secret ``` This path is particularly useful if you have **multiple Claude credentials** From d5e7794e43415b4143621c1cc23f238af84d74cb Mon Sep 17 00:00:00 2001 From: Ryan Born Date: Wed, 23 Sep 2026 13:01:37 -0400 Subject: [PATCH 4/8] P12 review round 2: update config.exs flag comment and add phase-table shipped annotations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fix stale :provider_accounts_enabled comment in config/config.exs: it previously claimed "nothing reads it yet" but P3+ actually source credentials from accounts when the flag is true. Update to reflect current behavior and the fallback chain. - Add (**shipped**) annotations to phase table (§10) for P0, P1, P2, P4, P6, P9 for consistency with Status line asserting "P0–P11 all shipped". Use bd-... issue IDs where available (P2: bd-77j2if, P4: bd-6yb06i). Addresses reviewer findings from P12 acceptance criteria round 2. --- config/config.exs | 12 ++++++------ docs/provider-account-design.md | 12 ++++++------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/config/config.exs b/config/config.exs index c2c33ea6..82d3aec8 100644 --- a/config/config.exs +++ b/config/config.exs @@ -119,15 +119,15 @@ config :arbiter_web, :quota_bar_colors, # `quota_get` tool is invoked. Enabled by default; `config/test.exs` turns it # off so the quota surface stays a pure DB read under test. # Provider accounts (docs/provider-account-design.md §7.5, bd-77j2if). The -# read-flip switch for phase P3: when true, `ConfigDir.oauth_token/1` and -# `WorkerEnv.resolve/1` will source provider credentials from +# read-flip switch for phase P3 (bd-aiodva) onward: when true, `ConfigDir.oauth_token/1` +# `ConfigDir.env/1` and `WorkerEnv.resolve/1` source provider credentials from # `provider_accounts` / `provider_credentials` instead of the workspace's # `worker_env` blob. # -# It is false here and **nothing reads it yet**. P2 (this release) is the -# additive half of the three-release plan: it populates the new tables and -# writes an encrypted backup row, but every existing read path still comes off -# `workspaces.encrypted_worker_env`. Rolling P2 back is "drop the new tables". +# It is false here by default. P3+ read from accounts when this flag is true; +# the fallback chain (§7.5) handles flag-off for legacy deployments. +# P2 (bd-77j2if) was the additive setup: it populates the new tables and writes +# an encrypted backup row. Rolling P2 back is "drop the new tables". config :arbiter, :provider_accounts_enabled, false config :arbiter, :cloud_code_quota, enabled: true diff --git a/docs/provider-account-design.md b/docs/provider-account-design.md index 583fd2a6..2c3f954b 100644 --- a/docs/provider-account-design.md +++ b/docs/provider-account-design.md @@ -793,16 +793,16 @@ Each phase is sized to be one child ticket. | Phase | What | Depends on | Priority | Difficulty | |---|---|---|---|---| -| **P0** | `mix arbiter.accounts.census` — read-only; fingerprints and key names only; emits the candidate plan | — | P2 | D2 | -| **P1** | `ProviderAccount` / `ProviderCredential` / `WorkspaceProviderAccount` resources + migration. Tables only; nothing reads them | P0 | P2 | D2 | -| **P2** | Plan-driven extraction: move allowlisted keys, encrypted backup row, `mix arbiter.accounts.rollback`. Flag off; workspace blob still authoritative | P1 | P2 | D3 | +| **P0** | `mix arbiter.accounts.census` — read-only; fingerprints and key names only; emits the candidate plan (**shipped**) | — | P2 | D2 | +| **P1** | `ProviderAccount` / `ProviderCredential` / `WorkspaceProviderAccount` resources + migration. Tables only; nothing reads them (**shipped**) | P0 | P2 | D2 | +| **P2** | Plan-driven extraction: move allowlisted keys, encrypted backup row, `mix arbiter.accounts.rollback`. Flag off; workspace blob still authoritative (**shipped**, bd-77j2if) | P1 | P2 | D3 | | **P3** | Read-path flip behind `:provider_accounts_enabled` — `ConfigDir.oauth_token/1`, `ConfigDir.env/1`, `WorkerEnv.resolve/1` source from the account (**shipped**, bd-aiodva; see §7.5) | P2 | P2 | D3 | -| **P4** | Destructive step: remove moved keys from `worker_env`; delete `ConfigDir`'s server-env and install-wide-unambiguous fallbacks | P3 | P2 | D2 | +| **P4** | Destructive step: remove moved keys from `worker_env`; delete `ConfigDir`'s server-env and install-wide-unambiguous fallbacks (**shipped**, bd-6yb06i) | P3 | P2 | D2 | | **P5** | Re-key the three quota tables to `(provider_account_id, provider)`; per-column-group collapse (§6) (**shipped**, bd-3yokey) | P3, bd-b0zody, bd-7cvh8z | **P1** | D3 | -| **P6** | Build account iteration in the probes: `CloudProbe` fetches `/api/oauth/usage` once per account (bd-4fbpto deleted bd-5xuneh's per-token grouping; this is new code, not a re-key of it — §9); `OAuthUsage` cooldown keyed by account | P5 | P2 | D2 | +| **P6** | Build account iteration in the probes: `CloudProbe` fetches `/api/oauth/usage` once per account (bd-4fbpto deleted bd-5xuneh's per-token grouping; this is new code, not a re-key of it — §9); `OAuthUsage` cooldown keyed by account (**shipped**) | P5 | P2 | D2 | | **P7** | Account-wide quota hold: `QuotaGate` callback takes an account (**breaking behaviour change**); thresholds `min(account, workspace)` | P5 | **P1** | D3 | | **P8** | Account concurrency ceiling + per-workspace share; registry-derived live count; `Board.Snapshot` folds it in (**shipped**, bd-1k6pgv) | P7 | P2 | D3 | -| **P9** | `usage_events.provider_account_id` + `provider_credential_id` + backfill | bd-adyhvn, P2 | P2 | D2 | +| **P9** | `usage_events.provider_account_id` + `provider_credential_id` + backfill (**shipped**) | bd-adyhvn, P2 | P2 | D2 | | **P10** | `arb usage --by account` / `--account`; `arb quota --account`; JSON + LiveView surfaces (**shipped**, bd-icwk2k) | P9, P5 | P3 | D2 | | **P11** | `arb account` CLI: list / show / create / attach / rotate / **merge** (§2.5) (**shipped**, bd-8zvh5a) | P2 | P2 | D2 | | **P12** | Docs + moduledocs: retire the "quota is per workspace" mental model | P10 | P3 | D1 | From 3ca1195e6f8267265fe925e76afb7b5a039992dc Mon Sep 17 00:00:00 2001 From: Ryan Born Date: Wed, 23 Sep 2026 13:05:29 -0400 Subject: [PATCH 5/8] P12 review round 2: annotate P7 as shipped in phase table MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add (**shipped**) annotation to P7 row in the phase table (§10) for consistency with Status line asserting "P0–P11 all shipped". P7 (account-wide quota hold: QuotaGate callback takes an account) was delivered in #1949. Addresses final reviewer finding from P12 acceptance criteria round 2. --- docs/provider-account-design.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/provider-account-design.md b/docs/provider-account-design.md index 2c3f954b..7ec413f4 100644 --- a/docs/provider-account-design.md +++ b/docs/provider-account-design.md @@ -800,7 +800,7 @@ Each phase is sized to be one child ticket. | **P4** | Destructive step: remove moved keys from `worker_env`; delete `ConfigDir`'s server-env and install-wide-unambiguous fallbacks (**shipped**, bd-6yb06i) | P3 | P2 | D2 | | **P5** | Re-key the three quota tables to `(provider_account_id, provider)`; per-column-group collapse (§6) (**shipped**, bd-3yokey) | P3, bd-b0zody, bd-7cvh8z | **P1** | D3 | | **P6** | Build account iteration in the probes: `CloudProbe` fetches `/api/oauth/usage` once per account (bd-4fbpto deleted bd-5xuneh's per-token grouping; this is new code, not a re-key of it — §9); `OAuthUsage` cooldown keyed by account (**shipped**) | P5 | P2 | D2 | -| **P7** | Account-wide quota hold: `QuotaGate` callback takes an account (**breaking behaviour change**); thresholds `min(account, workspace)` | P5 | **P1** | D3 | +| **P7** | Account-wide quota hold: `QuotaGate` callback takes an account (**breaking behaviour change**); thresholds `min(account, workspace)` (**shipped**) | P5 | **P1** | D3 | | **P8** | Account concurrency ceiling + per-workspace share; registry-derived live count; `Board.Snapshot` folds it in (**shipped**, bd-1k6pgv) | P7 | P2 | D3 | | **P9** | `usage_events.provider_account_id` + `provider_credential_id` + backfill (**shipped**) | bd-adyhvn, P2 | P2 | D2 | | **P10** | `arb usage --by account` / `--account`; `arb quota --account`; JSON + LiveView surfaces (**shipped**, bd-icwk2k) | P9, P5 | P3 | D2 | From 9b7b78956c008407793b917c17c58abf3d58a0ad Mon Sep 17 00:00:00 2001 From: Ryan Born Date: Wed, 23 Sep 2026 13:09:16 -0400 Subject: [PATCH 6/8] P12 review round 3: fix stale workspace-centric language in Quota.Gate moduledoc The gate.ex moduledoc opening description said the gate "reads the latest quota snapshot for the workspace" (P5/P7-era phrasing), contradicting the correct account-keyed lookup in the same file and the actual call site at Worker.Dispatch.dispatch/2 (which uses account_id). Reword to "for the account whose credential this dispatch will authenticate with" to reflect the account model introduced in P5 and clarify the semantic. --- apps/arbiter/lib/arbiter/quota/gate.ex | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/arbiter/lib/arbiter/quota/gate.ex b/apps/arbiter/lib/arbiter/quota/gate.ex index 5932a0b7..42cb92ff 100644 --- a/apps/arbiter/lib/arbiter/quota/gate.ex +++ b/apps/arbiter/lib/arbiter/quota/gate.ex @@ -5,9 +5,9 @@ defmodule Arbiter.Quota.Gate do The gate is the single choke point the fleet dispatcher (`Arbiter.Worker.Dispatch.dispatch/2`) consults before mutating any task state, so a near-cap decision covers every dispatch path at once. It reads the - latest quota snapshot for the workspace **and the provider this dispatch will - actually run on** (bd-2mpo3f) and decides what to do when that provider nears - / crosses its primary window cap: + latest quota snapshot for the account whose credential this dispatch will + authenticate with, **and the provider this dispatch will actually run on** (bd-2mpo3f) + and decides what to do when that provider nears / crosses its primary window cap: * `:allow` — dispatch proceeds normally (there is headroom, or we are failing open because no snapshot exists). From 0c5b40129cef614ca3a703e875428717e7c59a60 Mon Sep 17 00:00:00 2001 From: Ryan Born Date: Wed, 23 Sep 2026 13:20:18 -0400 Subject: [PATCH 7/8] Update concurrency-cap copy assertion for the account-ceiling wording Co-Authored-By: Claude Opus 5.5 --- .../test/arbiter_web/live/workspace_config_screen_test.exs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/arbiter_web/test/arbiter_web/live/workspace_config_screen_test.exs b/apps/arbiter_web/test/arbiter_web/live/workspace_config_screen_test.exs index 582ba561..feb53373 100644 --- a/apps/arbiter_web/test/arbiter_web/live/workspace_config_screen_test.exs +++ b/apps/arbiter_web/test/arbiter_web/live/workspace_config_screen_test.exs @@ -116,7 +116,8 @@ defmodule ArbiterWeb.WorkspaceConfigScreenTest do {:ok, _view, html} = live(conn, ~p"/workspaces/#{ws.id}") assert html =~ "Max concurrent workers" - assert html =~ "lowest of this, the system cap and quota headroom" + assert html =~ "lowest of this, the account ceiling" + assert html =~ "the system cap and quota headroom" end test "auto-dispatch describes the scheduler, not manual dispatch", %{conn: conn} do From 2e2ccd1b704e98e410921685958e67a1db16074b Mon Sep 17 00:00:00 2001 From: Ryan Born Date: Wed, 23 Sep 2026 13:51:44 -0400 Subject: [PATCH 8/8] Sync drain test on the Registry pid partition, not its supervisor :sys.get_state(Drain.Registry) hits the Registry's supervisor, so it never waited for the partition that processes the killed caller's :EXIT; the status read could race the cleanup and see :draining in CI. Co-Authored-By: Claude Opus 5.5 --- apps/arbiter/test/arbiter/board/drain_test.exs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/apps/arbiter/test/arbiter/board/drain_test.exs b/apps/arbiter/test/arbiter/board/drain_test.exs index 39e7688f..681c04ed 100644 --- a/apps/arbiter/test/arbiter/board/drain_test.exs +++ b/apps/arbiter/test/arbiter/board/drain_test.exs @@ -204,8 +204,10 @@ defmodule Arbiter.Board.DrainTest do Process.exit(pid, :kill) assert_receive {:DOWN, ^ref, :process, ^pid, :killed} - # Registry cleans up on its own :DOWN — sync on it before re-reading. - _ = :sys.get_state(Drain.Registry) + # The Registry's (single) pid partition is linked to the caller and + # cleans up on its :EXIT — sync on that process, not the Registry's + # supervisor (which is what the bare registry name resolves to). + _ = :sys.get_state(Module.concat(Drain.Registry, "PIDPartition0")) assert Drain.status(autopilot: ap, supervisor: sup).state == :quiescent end