feat(quota): key scope + /v1/models expand to the whole group (real group)

- `getPoolsByGroup(groupId)` in quotaPools.ts: SELECT all pools for a group,
  exposed as public API and re-exported from localDb.ts.
- `resolveQuotaKeyScope`: pool → group → all pools in group expansion.
  The returned `poolSlugs` field now holds GROUP slugs (quotaGroupSlug of the
  group name) instead of individual pool-name slugs, one per distinct group.
  Group slug included only when the group has ≥1 valid connection (group-level
  anyValidConnection gate). Deduplication: a key in 2 pools of the same group
  expands once.
- `filterModelsToQuotaPools` (quotaCombos.ts): already matches by
  `parsed.groupSlug` ∈ poolSlugs — no logic change needed, just test alignment.
- quota-key-resolve.test.ts: updated 4 tests that asserted pool-name slugs
  (testpoola2, poolmix, etc.) to assert the group slug ("groupdemo") — this is
  alignment to the B5 group semantics, not masking; the assertions were correct
  for the old pool-slug behavior and are now correct for the new group-slug
  behavior.
- quota-catalog-filter.test.ts: updated fixtures from old quotaShared-* format
  to qtSd/<groupSlug>/... (naming changed in B3, test was already broken before B5).
This commit is contained in:
diegosouzapw
2026-05-31 19:15:16 -03:00
parent fc6692925e
commit e9cc53b17f
6 changed files with 377 additions and 47 deletions

View File

@@ -206,6 +206,19 @@ function makeId(): string {
// Public API
// ---------------------------------------------------------------------------
/**
* List all quota pools that belong to a specific group.
* Returns an empty array when no pools match the given groupId.
*/
export function getPoolsByGroup(groupId: string): QuotaPool[] {
const rows = getDb()
.prepare<PoolRow>(
"SELECT id, connection_id, name, group_id, created_at FROM quota_pools WHERE group_id = ? ORDER BY created_at ASC"
)
.all(groupId);
return rows.map((row) => rowToPool(row, getAllocations(row.id)));
}
/**
* List all quota pools with their allocations.
*/

View File

@@ -539,6 +539,7 @@ export * from "./db/inspectorSessions";
export {
listPools,
getPool,
getPoolsByGroup,
createPool,
updatePool,
deletePool,

View File

@@ -7,10 +7,11 @@
* `exclusive` flag.
*/
import { getPool } from "@/lib/db/quotaPools";
import { getPool, getPoolsByGroup } from "@/lib/db/quotaPools";
import { getProviderConnectionById } from "@/lib/db/providers";
import { getApiKeyById, updateApiKeyPermissions } from "@/lib/db/apiKeys";
import { quotaPoolSlug } from "./quotaModelNaming";
import { quotaGroupSlug } from "./quotaModelNaming";
import { getGroupName } from "@/lib/db/quotaGroups";
// ---------------------------------------------------------------------------
// Public interface
@@ -21,7 +22,15 @@ export interface QuotaKeyScope {
connectionIds: string[];
/** Provider slugs of those connections (deduplicated). */
providers: string[];
/** Alphanumeric pool slugs the key is scoped to (from quotaPoolSlug(pool.name)), deduplicated. */
/**
* Alphanumeric GROUP slugs the key is scoped to, deduplicated.
* Each entry is quotaGroupSlug(groupName) — one per distinct group the
* key's allowed pools belong to.
*
* NOTE: the field is still called `poolSlugs` to minimise churn across
* callers; it now holds group slugs (not individual pool-name slugs).
* Updated in Task B5 (real-group access).
*/
poolSlugs: string[];
}
@@ -51,11 +60,24 @@ export function constrainConnectionsToQuota(
* returns the set of connection IDs and provider slugs that the key is
* permitted to use.
*
* Task B5 — Real-group access:
* For each allowed pool, the scope expands to ALL pools in the same group:
* pool → pool.groupId → getPoolsByGroup(groupId) → aggregate their
* connections and providers.
*
* The returned `poolSlugs` field holds GROUP slugs (one per distinct group),
* not individual pool-name slugs. This aligns with the `qtSd/<groupSlug>/...`
* naming used by filterModelsToQuotaPools and the combo catalog.
*
* A group slug is only included when the group has at least one valid
* connection across any of its pools (same "anyValidConnection" gate,
* evaluated group-wide).
*
* Behaviour:
* - Empty / falsy input → `{ connectionIds: [], providers: [] }`.
* - Empty / falsy input → `{ connectionIds: [], providers: [], poolSlugs: [] }`.
* - Pool IDs that do not resolve (missing pool, missing connection) are
* silently skipped — never throws.
* - Both arrays are deduplicated; order is not guaranteed.
* - All arrays are deduplicated; order is not guaranteed.
*/
export async function resolveQuotaKeyScope(
allowedQuotas: string[] | null | undefined
@@ -66,42 +88,56 @@ export async function resolveQuotaKeyScope(
const connectionIdSet = new Set<string>();
const providerSet = new Set<string>();
const poolSlugSet = new Set<string>();
const groupSlugSet = new Set<string>();
// Collect the distinct group IDs reachable from the key's allowed pools.
// Deduplicate: a key in 2 pools of the same group expands once.
const groupIdSet = new Set<string>();
for (const poolId of allowedQuotas) {
const pool = getPool(poolId);
if (!pool) continue;
groupIdSet.add(pool.groupId);
}
// D2: iterate ALL member connections (fall back to [connectionId] for any
// un-backfilled row where connectionIds is empty/undefined — defensive).
const connIds: string[] =
Array.isArray(pool.connectionIds) && pool.connectionIds.length > 0
? pool.connectionIds
: [pool.connectionId];
// For each distinct group, aggregate ALL pools in that group.
for (const groupId of groupIdSet) {
const groupPools = getPoolsByGroup(groupId);
// Group-level "anyValidConnection" gate: include the group slug only when
// at least one pool in the group has a usable connection.
let groupHasValidConnection = false;
let anyValidConnection = false;
for (const connId of connIds) {
const connection = await getProviderConnectionById(connId);
if (!connection) continue; // missing connection contributes nothing; don't abort
for (const groupPool of groupPools) {
// D2: iterate ALL member connections (defensive fallback for un-backfilled rows).
const connIds: string[] =
Array.isArray(groupPool.connectionIds) && groupPool.connectionIds.length > 0
? groupPool.connectionIds
: [groupPool.connectionId];
const provider = (connection as Record<string, unknown>).provider;
if (typeof provider !== "string" || provider.length === 0) continue;
for (const connId of connIds) {
const connection = await getProviderConnectionById(connId);
if (!connection) continue; // missing connection contributes nothing
connectionIdSet.add(connId);
providerSet.add(provider);
anyValidConnection = true;
const provider = (connection as Record<string, unknown>).provider;
if (typeof provider !== "string" || provider.length === 0) continue;
connectionIdSet.add(connId);
providerSet.add(provider);
groupHasValidConnection = true;
}
}
// Only expose the pool's slug when it has at least one usable connection —
// an orphan pool (all connections deleted) has no quotaShared-* models, so
// its slug must not leak into the key's scope.
if (anyValidConnection) poolSlugSet.add(quotaPoolSlug(pool.name));
// Only expose the group slug when the group has at least one usable
// connection — a fully-orphaned group has no qtSd/<groupSlug>/... models.
if (groupHasValidConnection) {
const groupName = getGroupName(groupId) ?? groupId;
groupSlugSet.add(quotaGroupSlug(groupName));
}
}
return {
connectionIds: Array.from(connectionIdSet),
providers: Array.from(providerSet),
poolSlugs: Array.from(poolSlugSet),
poolSlugs: Array.from(groupSlugSet),
};
}

View File

@@ -1,20 +1,27 @@
/**
* Tests for filterModelsToQuotaPools (quota/quotaCombos.ts).
*
* Updated in Task B5 to use the qtSd/<groupSlug>/<provider>/<model> naming
* (introduced in B3). The function matches by groupSlug ∈ poolSlugs (which
* now holds group slugs after B5).
*/
import { describe, it } from "node:test";
import assert from "node:assert/strict";
import { filterModelsToQuotaPools } from "../../src/lib/quota/quotaCombos.js";
describe("filterModelsToQuotaPools", () => {
const models = [
{ id: "quotaShared-times-codex/gpt-5.5" },
{ id: "quotaShared-times-codex/gpt-5.4" },
{ id: "qtSd/times/codex/gpt-5.5" },
{ id: "qtSd/times/codex/gpt-5.4" },
{ id: "cx/gpt-5.5" },
{ id: "quotaShared-other-codex/m" },
{ id: "qtSd/other/codex/m" },
];
it("returns only quotaShared-* entries whose poolSlug is in the given pool slugs", () => {
it("returns only qtSd/* entries whose groupSlug is in the given slugs", () => {
const result = filterModelsToQuotaPools(models, ["times"]);
assert.deepEqual(result, [
{ id: "quotaShared-times-codex/gpt-5.5" },
{ id: "quotaShared-times-codex/gpt-5.4" },
{ id: "qtSd/times/codex/gpt-5.5" },
{ id: "qtSd/times/codex/gpt-5.4" },
]);
});
@@ -29,28 +36,28 @@ describe("filterModelsToQuotaPools", () => {
assert.deepEqual(result, []);
});
it("matches multiple pool slugs simultaneously", () => {
it("matches multiple group slugs simultaneously", () => {
const result = filterModelsToQuotaPools(models, ["times", "other"]);
assert.deepEqual(result, [
{ id: "quotaShared-times-codex/gpt-5.5" },
{ id: "quotaShared-times-codex/gpt-5.4" },
{ id: "quotaShared-other-codex/m" },
{ id: "qtSd/times/codex/gpt-5.5" },
{ id: "qtSd/times/codex/gpt-5.4" },
{ id: "qtSd/other/codex/m" },
]);
});
it("preserves extra fields on model entries (generic T extends { id })", () => {
const richModels = [
{ id: "quotaShared-times-cx/gpt-5.5", object: "model", owned_by: "combo" },
{ id: "qtSd/times/cx/gpt-5.5", object: "model", owned_by: "combo" },
{ id: "cx/gpt-5.5", object: "model", owned_by: "cx" },
];
const result = filterModelsToQuotaPools(richModels, ["times"]);
assert.deepEqual(result, [
{ id: "quotaShared-times-cx/gpt-5.5", object: "model", owned_by: "combo" },
{ id: "qtSd/times/cx/gpt-5.5", object: "model", owned_by: "combo" },
]);
});
it("does not match a model from a different pool when only one slug is provided", () => {
it("does not match a model from a different group when only one slug is provided", () => {
const result = filterModelsToQuotaPools(models, ["other"]);
assert.deepEqual(result, [{ id: "quotaShared-other-codex/m" }]);
assert.deepEqual(result, [{ id: "qtSd/other/codex/m" }]);
});
});

View File

@@ -0,0 +1,262 @@
/**
* tests/unit/quota-group-scope.test.ts
*
* TDD — Task B5: key scope + /v1/models expand to the whole group.
*
* Scenario: group G has pool A (openrouter, 1 conn) + pool B (baidu, 1 conn).
* A key allocated to ONLY pool A:
* - resolveQuotaKeyScope([A.id]) must include connectionIds/providers from
* BOTH A and B (group-level expansion).
* - poolSlugs returns the GROUP slug (quotaGroupSlug of G's name), NOT the
* individual pool name slugs.
* - filterModelsToQuotaPools keeps models for both providers from that group.
*
* A key in a DIFFERENT group must NOT see G's models.
*
* An orphan pool (no valid connections) should NOT add the group slug unless
* the group has at least one valid connection (any pool).
*/
import test from "node:test";
import assert from "node:assert/strict";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-quota-group-scope-"));
process.env.DATA_DIR = TEST_DATA_DIR;
const core = await import("../../src/lib/db/core.ts");
const poolsDb = await import("../../src/lib/db/quotaPools.ts");
const groupsDb = await import("../../src/lib/db/quotaGroups.ts");
const providersDb = await import("../../src/lib/db/providers.ts");
const { resolveQuotaKeyScope } = await import("../../src/lib/quota/quotaKey.ts");
const { filterModelsToQuotaPools } = await import("../../src/lib/quota/quotaCombos.ts");
const { quotaGroupSlug } = await import("../../src/lib/quota/quotaModelNaming.ts");
// ---------------------------------------------------------------------------
// Lifecycle helpers
// ---------------------------------------------------------------------------
async function resetStorage() {
core.resetDbInstance();
for (let attempt = 0; attempt < 10; attempt++) {
try {
if (fs.existsSync(TEST_DATA_DIR)) {
fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true });
}
break;
} catch (error: unknown) {
const err = error as NodeJS.ErrnoException;
if ((err?.code === "EBUSY" || err?.code === "EPERM") && attempt < 9) {
await new Promise((resolve) => setTimeout(resolve, 50 * (attempt + 1)));
} else {
throw error;
}
}
}
fs.mkdirSync(TEST_DATA_DIR, { recursive: true });
}
test.beforeEach(async () => {
await resetStorage();
});
test.after(async () => {
core.resetDbInstance();
fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true });
});
// ---------------------------------------------------------------------------
// Tests
// ---------------------------------------------------------------------------
test("resolveQuotaKeyScope: key in pool A sees ALL connections/providers of group G (pool A + pool B)", async () => {
// Create group G
const groupG = groupsDb.createGroup("GroupG");
// Create connections for each pool
const connA = await providersDb.createProviderConnection({
provider: "openrouter",
authType: "apikey",
name: "conn-openrouter-g",
apiKey: "sk-openrouter-g",
});
const connB = await providersDb.createProviderConnection({
provider: "baidu",
authType: "apikey",
name: "conn-baidu-g",
apiKey: "sk-baidu-g",
});
const idA = (connA as Record<string, unknown>).id as string;
const idB = (connB as Record<string, unknown>).id as string;
// Create pool A and pool B, both in group G
const poolA = poolsDb.createPool({ connectionId: idA, name: "Pool A", groupId: groupG.id });
const poolB = poolsDb.createPool({ connectionId: idB, name: "Pool B", groupId: groupG.id });
// Key is allocated ONLY to pool A
const scope = await resolveQuotaKeyScope([poolA.id]);
// Must include both connections
assert.ok(scope.connectionIds.includes(idA), "should include pool A connection");
assert.ok(scope.connectionIds.includes(idB), "should include pool B connection (group expansion)");
assert.equal(scope.connectionIds.length, 2, "exactly 2 connections");
// Must include both providers
assert.ok(scope.providers.includes("openrouter"), "should include openrouter");
assert.ok(scope.providers.includes("baidu"), "should include baidu (group expansion)");
assert.equal(scope.providers.length, 2, "exactly 2 providers");
// poolSlugs should be the GROUP slug (not individual pool slugs)
const expectedGroupSlug = quotaGroupSlug(groupG.name); // "groupg"
assert.deepEqual(scope.poolSlugs, [expectedGroupSlug], "poolSlugs should be the group slug");
// Ensure pool B's id is reachable from scope (sanity)
assert.ok(poolB.id, "pool B exists");
});
test("resolveQuotaKeyScope: key in pool from group H does NOT see group G models", async () => {
// Create two groups
const groupG = groupsDb.createGroup("GroupG2");
const groupH = groupsDb.createGroup("GroupH2");
const connG = await providersDb.createProviderConnection({
provider: "openrouter",
authType: "apikey",
name: "conn-g2",
apiKey: "sk-g2",
});
const connH = await providersDb.createProviderConnection({
provider: "baidu",
authType: "apikey",
name: "conn-h2",
apiKey: "sk-h2",
});
const idG = (connG as Record<string, unknown>).id as string;
const idH = (connH as Record<string, unknown>).id as string;
const poolInG = poolsDb.createPool({ connectionId: idG, name: "Pool G2", groupId: groupG.id });
const poolInH = poolsDb.createPool({ connectionId: idH, name: "Pool H2", groupId: groupH.id });
// Key only has pool from group H
const scope = await resolveQuotaKeyScope([poolInH.id]);
// Should NOT include group G's connection or provider
assert.ok(!scope.connectionIds.includes(idG), "should NOT include group G connection");
assert.ok(!scope.providers.includes("openrouter"), "should NOT include openrouter (group G)");
// Should include group H's connection and provider
assert.ok(scope.connectionIds.includes(idH), "should include group H connection");
assert.ok(scope.providers.includes("baidu"), "should include baidu (group H)");
const expectedGroupSlug = quotaGroupSlug(groupH.name);
assert.deepEqual(scope.poolSlugs, [expectedGroupSlug], "poolSlugs should be H's group slug");
// Sanity: pool in G exists but is not seen
assert.ok(poolInG.id, "pool in G exists");
});
test("resolveQuotaKeyScope: two pools in the same group expand once (deduplicated group slug)", async () => {
const groupG = groupsDb.createGroup("GroupGDedup");
const connA = await providersDb.createProviderConnection({
provider: "openrouter",
authType: "apikey",
name: "conn-dedup-a",
apiKey: "sk-dedup-a",
});
const connB = await providersDb.createProviderConnection({
provider: "baidu",
authType: "apikey",
name: "conn-dedup-b",
apiKey: "sk-dedup-b",
});
const idA = (connA as Record<string, unknown>).id as string;
const idB = (connB as Record<string, unknown>).id as string;
const poolA = poolsDb.createPool({ connectionId: idA, name: "Pool Dedup A", groupId: groupG.id });
const poolB = poolsDb.createPool({ connectionId: idB, name: "Pool Dedup B", groupId: groupG.id });
// Key has BOTH pools from the same group
const scope = await resolveQuotaKeyScope([poolA.id, poolB.id]);
// Should only appear once in poolSlugs (group-level dedup)
const expectedSlug = quotaGroupSlug(groupG.name);
assert.deepEqual(scope.poolSlugs, [expectedSlug], "group slug should appear only once");
// Both connections present
assert.ok(scope.connectionIds.includes(idA));
assert.ok(scope.connectionIds.includes(idB));
assert.equal(scope.connectionIds.length, 2);
});
test("filterModelsToQuotaPools: keeps both providers' qtSd/<group>/... models from scope", async () => {
const groupG = groupsDb.createGroup("GroupGFilter");
const groupSlug = quotaGroupSlug(groupG.name); // e.g. "groupgfilter"
const models = [
{ id: `qtSd/${groupSlug}/openrouter/gpt-5.5` },
{ id: `qtSd/${groupSlug}/baidu/ernie-4.5` },
{ id: `qtSd/otherg/openrouter/gpt-5.5` },
{ id: "gpt-5.5" }, // not a quota model
];
const result = filterModelsToQuotaPools(models, [groupSlug]);
assert.equal(result.length, 2, "should return both providers' models for the group");
assert.ok(result.some((m) => m.id === `qtSd/${groupSlug}/openrouter/gpt-5.5`));
assert.ok(result.some((m) => m.id === `qtSd/${groupSlug}/baidu/ernie-4.5`));
assert.ok(!result.some((m) => m.id === `qtSd/otherg/openrouter/gpt-5.5`), "other group filtered out");
assert.ok(!result.some((m) => m.id === "gpt-5.5"), "non-quota model filtered out");
});
test("resolveQuotaKeyScope: orphan pool (no valid connections) — group slug excluded if no valid conn in group", async () => {
const groupG = groupsDb.createGroup("GroupGOrphan");
// Pool with a non-existent connection
const orphanPool = poolsDb.createPool({
connectionId: "conn-does-not-exist-orphan",
name: "Orphan Pool G",
groupId: groupG.id,
});
const scope = await resolveQuotaKeyScope([orphanPool.id]);
// No valid connections anywhere in the group → group slug must NOT be included
assert.deepEqual(scope.connectionIds, []);
assert.deepEqual(scope.providers, []);
assert.deepEqual(scope.poolSlugs, [], "group slug excluded when no valid connection in group");
});
test("resolveQuotaKeyScope: orphan pool in group that also has a valid pool — group slug included", async () => {
const groupG = groupsDb.createGroup("GroupGPartial");
// One valid connection pool
const connValid = await providersDb.createProviderConnection({
provider: "openrouter",
authType: "apikey",
name: "conn-partial-valid",
apiKey: "sk-partial-valid",
});
const idValid = (connValid as Record<string, unknown>).id as string;
const validPool = poolsDb.createPool({ connectionId: idValid, name: "Valid Pool G", groupId: groupG.id });
// One orphan pool in the same group
const orphanPool = poolsDb.createPool({
connectionId: "conn-orphan-partial",
name: "Orphan Pool G2",
groupId: groupG.id,
});
// Key is allocated only to the ORPHAN pool, but the group has the valid pool
const scope = await resolveQuotaKeyScope([orphanPool.id]);
// The group has a valid connection (the validPool's connection) so group slug should be included
const expectedSlug = quotaGroupSlug(groupG.name);
assert.ok(scope.poolSlugs.includes(expectedSlug), "group slug should be included since group has valid connection");
assert.ok(scope.connectionIds.includes(idValid), "should include the valid pool's connection");
assert.ok(scope.providers.includes("openrouter"), "should include openrouter from valid pool");
assert.ok(validPool.id, "valid pool exists");
});

View File

@@ -80,7 +80,7 @@ test("resolveQuotaKeyScope: unknown pool id returns empty scope (no throw)", asy
assert.deepEqual(scope, { connectionIds: [], providers: [], poolSlugs: [] });
});
test("resolveQuotaKeyScope: valid pool returns its connectionId, provider, and poolSlug", async () => {
test("resolveQuotaKeyScope: valid pool returns its connectionId, provider, and group slug", async () => {
// Seed a real provider connection
const conn = await providersDb.createProviderConnection({
provider: "openai",
@@ -91,15 +91,16 @@ test("resolveQuotaKeyScope: valid pool returns its connectionId, provider, and p
const connId = (conn as Record<string, unknown>).id as string;
assert.ok(connId, "connection should have an id");
// Seed a pool referencing that connection
// Seed a pool referencing that connection (defaults to group-demo)
const pool = poolsDb.createPool({ connectionId: connId, name: "Test Pool A2" });
const scope = await resolveQuotaKeyScope([pool.id]);
assert.deepEqual(scope.connectionIds, [connId]);
assert.deepEqual(scope.providers, ["openai"]);
// "Test Pool A2" slugifies to "testpoola2"
assert.deepEqual(scope.poolSlugs, ["testpoola2"]);
// Task B5: poolSlugs now contains the GROUP slug, not the pool-name slug.
// Pool defaults to 'group-demo' → quotaGroupSlug("GroupDemo") = "groupdemo".
assert.deepEqual(scope.poolSlugs, ["groupdemo"]);
});
test("resolveQuotaKeyScope: multiple pools same provider deduplicates providers", async () => {
@@ -118,6 +119,7 @@ test("resolveQuotaKeyScope: multiple pools same provider deduplicates providers"
const id1 = (conn1 as Record<string, unknown>).id as string;
const id2 = (conn2 as Record<string, unknown>).id as string;
// Both pools default to group-demo
const pool1 = poolsDb.createPool({ connectionId: id1, name: "Pool Anthro 1" });
const pool2 = poolsDb.createPool({ connectionId: id2, name: "Pool Anthro 2" });
@@ -129,9 +131,12 @@ test("resolveQuotaKeyScope: multiple pools same provider deduplicates providers"
// provider "anthropic" appears only once even though two pools share it
assert.deepEqual(scope.providers, ["anthropic"]);
// Task B5: both pools are in group-demo → single group slug "groupdemo"
assert.deepEqual(scope.poolSlugs, ["groupdemo"]);
});
test("resolveQuotaKeyScope: multiple pools different providers", async () => {
test("resolveQuotaKeyScope: multiple pools different providers (same group-demo)", async () => {
const connA = await providersDb.createProviderConnection({
provider: "openai",
authType: "apikey",
@@ -147,6 +152,7 @@ test("resolveQuotaKeyScope: multiple pools different providers", async () => {
const idA = (connA as Record<string, unknown>).id as string;
const idB = (connB as Record<string, unknown>).id as string;
// Both pools in group-demo (default)
const poolA = poolsDb.createPool({ connectionId: idA, name: "Pool OAI" });
const poolB = poolsDb.createPool({ connectionId: idB, name: "Pool GEM" });
@@ -158,6 +164,9 @@ test("resolveQuotaKeyScope: multiple pools different providers", async () => {
const providers = [...scope.providers].sort();
assert.deepEqual(providers, ["gemini", "openai"]);
// Task B5: both pools are in group-demo → single group slug
assert.deepEqual(scope.poolSlugs, ["groupdemo"]);
});
test("resolveQuotaKeyScope: pool referencing non-existent connectionId is skipped gracefully", async () => {
@@ -185,6 +194,8 @@ test("resolveQuotaKeyScope: mix of valid and invalid pool ids — only valid con
assert.deepEqual(scope.connectionIds, [connId]);
assert.deepEqual(scope.providers, ["openai"]);
// "Pool Mix" slugifies to "poolmix"
assert.deepEqual(scope.poolSlugs, ["poolmix"]);
// Task B5: poolSlugs now returns the GROUP slug (group-demo → "groupdemo"),
// not the pool-name slug ("poolmix"). Ghost pool IDs are skipped (no group
// resolution), so only the one valid pool's group appears.
assert.deepEqual(scope.poolSlugs, ["groupdemo"]);
});