From 73d340833055d172518357b9b17eb1155ba41df5 Mon Sep 17 00:00:00 2001 From: Robin Date: Thu, 30 Jul 2026 14:32:33 +0200 Subject: [PATCH 1/2] fix(core): preserve ineligible routing diagnostics --- packages/core/test/selection.test.ts | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/packages/core/test/selection.test.ts b/packages/core/test/selection.test.ts index b71ebfb..863a681 100644 --- a/packages/core/test/selection.test.ts +++ b/packages/core/test/selection.test.ts @@ -228,16 +228,25 @@ describe("selectAccount", () => { Effect.gen(function* () { const failure = yield* Effect.flip( selectAccount({ - candidates: [candidate("unknown", { usage: false })], + candidates: [ + candidate("unknown", { usage: false }), + candidate("short-exhausted", { shortUsed: 91 }) + ], config: defaultRoutingConfig, now }) ) expect(failure).toBeInstanceOf(NoEligibleAccountsError) - expect(failure.explanations).toHaveLength(1) - expect(failure.explanations[0]?.accountId).toBe("unknown") - expect(failure.explanations[0]?.rejection.valueOrUndefined).toBe("usage_unknown") + expect( + failure.explanations.map((explanation) => [ + explanation.accountId, + Option.getOrUndefined(explanation.rejection) + ]) + ).toEqual([ + [AccountId.make("unknown"), "usage_unknown"], + [AccountId.make("short-exhausted"), "short_headroom"] + ]) }) ) }) From 3c2686799e10208497ea9c3bcc95512428bffdf7 Mon Sep 17 00:00:00 2001 From: "no-mistakes[bot]" Date: Thu, 30 Jul 2026 14:41:50 +0200 Subject: [PATCH 2/2] no-mistakes(document): Documented rejection diagnostics; focused lint clean --- docs/architecture.md | 5 +++++ packages/core/test/selection.test.ts | 19 +++++++++++++++++-- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/docs/architecture.md b/docs/architecture.md index 8800227..f61dccb 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -179,6 +179,11 @@ reservations, then opaque account ID. Anonymous traffic is balanced but never assigned using inferred identity. +When no candidate is eligible, the portable core returns a typed `NoEligibleAccountsError` carrying +the complete `CandidateExplanation` array produced during selection, including each opaque account +ID and its rejection reason. AgentOS and other consumers should render those explanations rather +than reimplementing eligibility policy. + ## Generation-safe credential lifecycle Every subscription credential has a monotonically increasing router generation. diff --git a/packages/core/test/selection.test.ts b/packages/core/test/selection.test.ts index 863a681..e559113 100644 --- a/packages/core/test/selection.test.ts +++ b/packages/core/test/selection.test.ts @@ -229,8 +229,18 @@ describe("selectAccount", () => { const failure = yield* Effect.flip( selectAccount({ candidates: [ + candidate("reauth", { requiresReauthentication: true }), + candidate("blocked", { + block: AccountBlock.make({ + kind: "quota", + retryAt: now + hour + }) + }), candidate("unknown", { usage: false }), - candidate("short-exhausted", { shortUsed: 91 }) + candidate("short-exhausted", { shortUsed: 91 }), + candidate("weekly-exhausted", { weeklyUsed: 98 }), + candidate("too-old", { observedAt: now - day - 1 }), + candidate("expired-reset", { weeklyResetAt: now }) ], config: defaultRoutingConfig, now @@ -244,8 +254,13 @@ describe("selectAccount", () => { Option.getOrUndefined(explanation.rejection) ]) ).toEqual([ + [AccountId.make("reauth"), "reauthentication_required"], + [AccountId.make("blocked"), "active_block"], [AccountId.make("unknown"), "usage_unknown"], - [AccountId.make("short-exhausted"), "short_headroom"] + [AccountId.make("short-exhausted"), "short_headroom"], + [AccountId.make("weekly-exhausted"), "weekly_headroom"], + [AccountId.make("too-old"), "usage_too_old"], + [AccountId.make("expired-reset"), "weekly_reset_elapsed"] ]) }) )