Ctrl K
ComunidadGuías…

test/chat-p2-preflight-admission.test.js

Rama master · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
// P2-7 (evidence/ReviewChatCore.md §P2-7): the DEVIN_CONNECT fast-fail
// preflight only asked the cooldown/RPM questions
// (isAllRateLimited / isAllTemporarilyUnavailable). It was blind to the
// dimensions the scheduler's own admission predicate adds — account
// maintenance (held for a background / language-server swap; live by default)
// and the per-account in-flight cap — and to the selector latch.
//
// So a pool that was unselectable for one of those reasons passed the
// preflight, spent the full queue wait in acquireConnectAccount, and then
// acquired null — which the connect path cannot tell apart from "no pool at
// all" and answers with the un-accounted env token. That is the P0-2
// conflation the preflight exists to prevent (chat.js's own comment: those two
// cases "must NOT be conflated").
//
// The fix reuses the scheduler's admission conjunction read-only in preflight.
// These exercise the real handler, so the assertion is the client-visible
// outcome: a fast 429 and ZERO upstream calls, never an env-token turn.

import { afterEach, beforeEach, describe, it } from 'node:test';
import assert from 'node:assert/strict';
import { handleChatCompletions, __setConnectDeps, __resetConnectDeps } from '../src/handlers/chat.js';
import { createRequestBudget } from '../src/request-budget.js';
import {
  addAccountByKey, removeAccount, getAccountInternal, setAccountTier, markSelectorIneligible,
  markRateLimited,
} from '../src/auth.js';

const FREE_SELECTOR = 'swe-1-6-slow';

const SAVED = {};
const ENV_KEYS = [
  'DEVIN_CONNECT', 'DEVIN_CONNECT_TOKEN', 'WINDSURF_API_KEY',
  'WINDSURFAPI_STRICT_MODEL', 'DEVIN_CONNECT_MAX_INFLIGHT_PER_ACCOUNT',
  'WINDSURFAPI_ERROR_RECOVERY_MS',
];
for (const k of ENV_KEYS) SAVED[k] = process.env[k];

const created = [];

function seed(label) {
  const a = addAccountByKey(`devin-session-token$p27-${label}-${Math.random().toString(36).slice(2)}`, label);
  created.push(a.id);
  setAccountTier(a.id, 'pro');
  return getAccountInternal(a.id);
}

/** Stub the connect client so an env-token fallback is observable: the handler
 *  only puts a token on `params` for a POOLED account. */
function stubUpstream() {
  const state = { hits: 0, pooledToken: undefined };
  __setConnectDeps({
    toChatCompletion: async (params) => {
      state.hits++;
      state.pooledToken = params.token;
      return {
        status: 200,
        body: {
          id: 'x', object: 'chat.completion', created: 0,
          choices: [{ index: 0, message: { role: 'assistant', content: 'SERVED' }, finish_reason: 'stop' }],
        },
      };
    },
  });
  return state;
}

function ask() {
  return handleChatCompletions(
    { model: FREE_SELECTOR, stream: false, messages: [{ role: 'user', content: 'hi' }] },
    { callerKey: '' },
  );
}

beforeEach(() => {
  process.env.DEVIN_CONNECT = '1';
  // An env token is present on purpose: before the fix it is exactly what a
  // blocked pool silently falls through to (devin-connect.js `token || env`).
  process.env.DEVIN_CONNECT_TOKEN = 'env-token-shape-only';
  delete process.env.WINDSURF_API_KEY;
  delete process.env.WINDSURFAPI_STRICT_MODEL;
  delete process.env.DEVIN_CONNECT_MAX_INFLIGHT_PER_ACCOUNT;
});

afterEach(() => {
  __resetConnectDeps();
  while (created.length) removeAccount(created.pop());
  for (const k of ENV_KEYS) {
    if (SAVED[k] === undefined) delete process.env[k];
    else process.env[k] = SAVED[k];
  }
});

describe('P2-7: preflight applies the scheduler admission conjunction', () => {
  it('fails fast with 429 when every entitled account is in maintenance', async () => {
    const a = seed('maint-a');
    const b = seed('maint-b');
    a._maintenance = 1; // background / language-server swap in progress
    b._maintenance = 1;
    const up = stubUpstream();

    const result = await ask();

    assert.equal(result.status, 429,
      'a pool that cannot be selected must fail fast, not queue for 30s and then use the env token');
    assert.equal(result.body.error.type, 'rate_limit_exceeded');
    assert.ok(result.body.error.retry_after_ms > 0, 'carries a retry_after hint');
    assert.ok(result.headers['Retry-After'], 'sets the Retry-After header');
    assert.equal(up.hits, 0, 'never touched upstream (the env token must not serve a pool request)');
  });

  it('fails fast when the selector is latched on A and the only peer is in maintenance', async () => {
    const a = seed('lat-a');
    const b = seed('lat-b');
    assert.equal(markSelectorIneligible(a.apiKey, FREE_SELECTOR), true,
      'precondition: B is admitted, so the latch is written');
    b._maintenance = 1;
    const up = stubUpstream();

    const result = await ask();

    assert.equal(result.status, 429, 'latch(A) + maintenance(B) must not degrade into an env-token turn');
    assert.equal(up.hits, 0);
  });

  it('fails fast when every entitled account is at its in-flight cap', async () => {
    process.env.DEVIN_CONNECT_MAX_INFLIGHT_PER_ACCOUNT = '1';
    const a = seed('cap-a');
    a._inflight = 1;
    a._inflightAt = Date.now();
    const up = stubUpstream();

    const result = await ask();

    assert.equal(result.status, 429, 'the in-flight cap is an admission dimension, not a queue hint');
    assert.equal(up.hits, 0);
  });

  it('still serves normally when one peer is in maintenance (no over-trigger)', async () => {
    const a = seed('ok-a');
    seed('ok-b')._maintenance = 1;
    const up = stubUpstream();

    const result = await ask();

    assert.equal(result.status, 200, 'a selectable account must still serve');
    assert.equal(result.body.choices[0].message.content, 'SERVED');
    assert.equal(up.hits, 1);
    assert.equal(up.pooledToken, a.apiKey, 'served by the pooled account, not the env token');
  });

  // getApiKey half-opens an 'error' account whose recovery TTL elapsed BEFORE it
  // applies the admission predicate. The preflight must model that step, or a
  // pool whose only remaining candidate is due for the trial would be 429'd
  // forever — the recovery runs inside getApiKey, so the 429 would never let it.
  it('does not fast-fail when the remaining peer is DUE for half-open recovery', async () => {
    process.env.WINDSURFAPI_ERROR_RECOVERY_MS = '60000';
    seed('maint-a')._maintenance = 1;
    const b = seed('recover-b');
    b.status = 'error';
    b._errorAt = Date.now() - 120_000; // past the 60s TTL → the next selection recovers it
    const up = stubUpstream();

    const result = await ask();

    assert.equal(result.status, 200, 'the next getApiKey would recover B — do not 429 the trial away');
    assert.equal(up.pooledToken, b.apiKey, 'served by the recovered account');
    assert.equal(getAccountInternal(b.id).status, 'active', 'the trial really ran');
  });

  it('fast-fails while the errored peer is still inside its recovery TTL', async () => {
    process.env.WINDSURFAPI_ERROR_RECOVERY_MS = '60000';
    seed('maint-a')._maintenance = 1;
    const b = seed('recover-b');
    b.status = 'error';
    b._errorAt = Date.now() - 1_000; // not due yet
    const up = stubUpstream();

    const result = await ask();

    assert.equal(result.status, 429, 'no account is selectable and none is due for a trial');
    assert.equal(up.hits, 0);
  });

  it('still falls back to the env token when the pool is genuinely EMPTY', async () => {
    const up = stubUpstream();

    const result = await ask();

    assert.equal(result.status, 200, 'an empty pool is the env-token deploy shape, not exhaustion');
    assert.equal(result.body.choices[0].message.content, 'SERVED');
    assert.equal(up.hits, 1);
    assert.equal(up.pooledToken, undefined, 'the empty-pool shape hands no pooled token to the client');
  });

  it('still fails fast for a fully rate-limited pool (existing arm unchanged)', async () => {
    const a = seed('rl-a');
    const b = seed('rl-b');
    markRateLimited(a.apiKey, 5 * 60 * 1000);
    markRateLimited(b.apiKey, 5 * 60 * 1000);
    const up = stubUpstream();

    const result = await ask();

    assert.equal(result.status, 429);
    assert.ok(result.body.error.retry_after_ms >= 60 * 1000, 'the precise cooldown is still advertised');
    assert.equal(up.hits, 0);
  });

  it('fast-fails 429 when the only account is cooled for the requested selector', async () => {
    // A model-scoped 429 with an upstream reset — the shape finalizeConnectAccount
    // writes for "Reached message rate limit for this model ... Resets in: 3h".
    // It benches the account for THIS selector only, so the preflight must read
    // BOTH dimensions: rl.allLimited and tu.allUnavailable true. If tu stayed
    // selector-blind, `degradable` would be true and the request would spend the
    // queue wait (and fall through to the env token) instead of a fast 429.
    const a = seed('sel-rl');
    markRateLimited(a.apiKey, 3 * 60 * 60 * 1000, FREE_SELECTOR, 'r');
    const up = stubUpstream();

    const result = await handleChatCompletions(
      { model: FREE_SELECTOR, stream: false, messages: [{ role: 'user', content: 'hi' }] },
      // Bound the regression path: without the fix this test would wait the full
      // 30s queue before answering.
      { callerKey: '', budget: createRequestBudget({ deadlineMs: 2_000 }) },
    );

    assert.equal(result.status, 429, 'a selector-cooled pool must fast-fail, not queue');
    assert.equal(result.body.error.type, 'rate_limit_exceeded');
    assert.ok(result.body.error.retry_after_ms >= 60 * 1000, 'the upstream reset is advertised');
    assert.ok(result.headers['Retry-After'], 'sets the Retry-After header');
    assert.equal(up.hits, 0, 'no upstream turn and no env-token fallback');
  });
});