test/auth-quota-cooldown-monotonic.test.js
Rama master · réplica de lectura del repositorio original.
// V-18 (audit CORE-004) — one quota cooldown dimension, two writers.
//
// * applyQuotaSnapshot() reacts to a proactive weekly-credit snapshot.
// * markQuotaExhausted() reacts to a LIVE 402 for an actual request.
//
// The audit probe: a live 402 armed a three-hour deadline; a later snapshot that
// reported 80% weekly cleared it, and a snapshot reporting 0% overwrote it with
// the 30-minute default. Both are wrong:
//
// 1. MONOTONIC — a cooldown is a deadline, not a value. A later write may
// extend it (more evidence of dryness) but must never pull it closer, or an
// account re-enters rotation before the upstream said it would be usable.
// 2. SAME-SOURCE PROOF — a weekly snapshot only knows the INCLUDED-quota
// dimension. A live 402 can be about the on-demand/ACU dimension while the
// weekly number reads healthy, so "weekly 80%" is not evidence that the
// dimension the 402 came from recovered. Only a snapshot that wrote the
// deadline may clear it; a live-402 deadline self-heals on expiry.
import { afterEach, describe, it } from 'node:test';
import assert from 'node:assert/strict';
import {
addAccountByKey, removeAccount, applyQuotaSnapshot, markQuotaExhausted,
__isRateLimitedForModel, __serializeAccounts, __deserializeAccount,
} from '../src/auth.js';
const created = [];
const ENV_KEYS = [
'WINDSURFAPI_QUOTA_COOLDOWN', 'WINDSURFAPI_QUOTA_COOLDOWN_MS',
'WINDSURFAPI_QUOTA_DRY_THRESHOLD', 'WINDSURFAPI_SPEND_ON_DEMAND',
];
const savedEnv = {};
function add(label) {
const account = addAccountByKey(
`v18-key-${Date.now()}-${created.length}-${Math.random().toString(36).slice(2)}`,
label,
);
created.push(account.id);
return account;
}
function persistEnv(key) { savedEnv[key] = process.env[key]; }
afterEach(() => {
for (const k of ENV_KEYS) {
if (!(k in savedEnv)) continue;
if (savedEnv[k] === undefined) delete process.env[k];
else process.env[k] = savedEnv[k];
delete savedEnv[k];
}
while (created.length) removeAccount(created.pop());
});
describe('V-18 — quota cooldown is monotonic and source-aware', () => {
it('weekly quota snapshot cannot shorten a live 402 cooldown', () => {
persistEnv('WINDSURFAPI_QUOTA_COOLDOWN_MS');
process.env.WINDSURFAPI_QUOTA_COOLDOWN_MS = '1800000'; // 30m snapshot default
const acct = add('v18-shorten');
const now = 30_000_000;
const liveMs = 3 * 60 * 60 * 1000; // upstream said 3h
markQuotaExhausted(acct.apiKey, liveMs, now);
const liveDeadline = acct.quotaResetAt;
assert.equal(liveDeadline, now + liveMs);
applyQuotaSnapshot(acct, 0, now + 1000); // still dry
assert.equal(acct.quotaResetAt, liveDeadline,
'a 30m snapshot must not pull a 3h live-402 deadline closer');
assert.equal(__isRateLimitedForModel(acct, null, liveDeadline - 1), true,
'the account stays out of rotation until the upstream window ends');
});
it('a recovered weekly snapshot does not clear a live-402 cooldown', () => {
const acct = add('v18-cross-source');
const now = 31_000_000;
markQuotaExhausted(acct.apiKey, 60 * 60 * 1000, now);
const liveDeadline = acct.quotaResetAt;
// Weekly data reads healthy — but it says nothing about the dimension the
// live 402 came from (it can be an on-demand/ACU exhaustion).
applyQuotaSnapshot(acct, 80, now + 1000);
assert.equal(acct.quotaResetAt, liveDeadline,
'unrelated weekly data cannot clear a live-402 cooldown');
assert.equal(__isRateLimitedForModel(acct, null, now + 2000), true,
'the account stays cooled');
});
it('a snapshot-written cooldown still clears on a recovered snapshot (control)', () => {
const acct = add('v18-same-source');
const now = 32_000_000;
applyQuotaSnapshot(acct, 0, now);
assert.ok(acct.quotaResetAt > now, 'dry snapshot cools');
applyQuotaSnapshot(acct, 42, now + 60_000);
assert.equal(acct.quotaResetAt, 0, 'same-source recovery clears it immediately');
assert.equal(acct.quotaResetSource, null, 'and the provenance is dropped with it');
});
it('a snapshot never shortens a longer snapshot cooldown either', () => {
persistEnv('WINDSURFAPI_QUOTA_COOLDOWN_MS');
process.env.WINDSURFAPI_QUOTA_COOLDOWN_MS = '7200000'; // 2h
const acct = add('v18-snapshot-monotonic');
const now = 33_000_000;
applyQuotaSnapshot(acct, 0, now);
const first = acct.quotaResetAt;
assert.equal(first, now + 7200000);
process.env.WINDSURFAPI_QUOTA_COOLDOWN_MS = '1800000'; // operator lowers it
applyQuotaSnapshot(acct, 0, now + 1000);
assert.equal(acct.quotaResetAt, first,
'a tunable change does not retroactively shorten an armed cooldown');
});
it('an expired live-402 cooldown is cleaned up by the next snapshot (self-healing)', () => {
const acct = add('v18-expire');
const now = 34_000_000;
markQuotaExhausted(acct.apiKey, 60 * 60 * 1000, now);
const liveDeadline = acct.quotaResetAt;
applyQuotaSnapshot(acct, 80, liveDeadline + 1);
assert.equal(acct.quotaResetAt, 0, 'an expired deadline is stale state, not a cooldown');
assert.equal(acct.quotaResetSource, null);
});
it('a spendable balance does not clear a live-402 deadline either (deliberate)', () => {
const acct = add('v18-funded-live');
const now = 36_000_000;
markQuotaExhausted(acct.apiKey, 30 * 60 * 1000, now);
const liveDeadline = acct.quotaResetAt;
acct.credits = { balance: 50 }; // policy says a funded account may serve
applyQuotaSnapshot(acct, 0, now + 1000);
assert.equal(acct.quotaResetAt, liveDeadline,
'the 402 window bounds the re-probe rate; only expiry clears it');
});
it('the live-402 provenance survives a restart', () => {
const acct = add('v18-persist');
const now = Date.now();
markQuotaExhausted(acct.apiKey, 60 * 60 * 1000, now);
const record = __serializeAccounts().find(a => a.apiKey === acct.apiKey);
assert.equal(record.quotaResetSource, 'live402',
'the source is persisted with the deadline');
const restored = __deserializeAccount(record);
assert.equal(restored.quotaResetAt, record.quotaResetAt);
assert.equal(restored.quotaResetSource, 'live402');
applyQuotaSnapshot(restored, 90, now + 1000);
assert.equal(restored.quotaResetAt, record.quotaResetAt,
'a restart must not make the live-402 deadline clearable by weekly data');
});
it('a legacy deadline with no recorded source is treated as not-snapshot-clearable', () => {
const now = 35_000_000;
// Written by a version that did not persist provenance (or hand-edited).
const legacy = __deserializeAccount({ apiKey: 'v18-legacy', quotaResetAt: now + 60_000 });
assert.equal(legacy.quotaResetSource, null, 'unknown provenance is null, not guessed');
applyQuotaSnapshot(legacy, 80, now + 1000);
assert.equal(legacy.quotaResetAt, now + 60_000,
'the snapshot cannot prove anything about a deadline it did not write');
applyQuotaSnapshot(legacy, 80, now + 120_000);
assert.equal(legacy.quotaResetAt, 0, 'expiry still self-heals it');
});
});