Description
In `src/interceptors/fetchInterceptor.ts`, `patchGlobalFetch` uses a module-level `isPatched` flag to prevent double-patching. However, `setBudgetManager` and `setModelRouter` are separate functions that update module-level variables after the patch:
```typescript
let isPatched = false;
let budgetManager: BudgetManager | null = null;
export function patchGlobalFetch(): void {
if (isPatched) {
return; // subsequent calls are no-ops
}
// patches globalThis.fetch once
isPatched = true;
}
export function setBudgetManager(manager: BudgetManager | null): void {
budgetManager = manager; // replaces previous manager silently
}
```
Impact
When an application calls `budgetGuard` twice (e.g. to update the monthly limit mid-month), the second call replaces `budgetManager` with a fresh instance that has `totalSpent = 0`. Because `isPatched = true`, the interceptor is already in place but now points to the new manager — effectively resetting the budget counter without any warning.
```typescript
budgetGuard({ monthlyLimit: 10, mode: 'block' });
// ... spend $9
budgetGuard({ monthlyLimit: 10, mode: 'block' }); // called again
// totalSpent resets to $0 in the new BudgetManager
// previous $9 spent is lost
```
Additionally, `unpatchGlobalFetch` (if it exists) must be called explicitly or `globalThis.fetch` is permanently patched for the process lifetime with no way to restore the original behavior.
Expected Behavior
Calling `budgetGuard` a second time should either:
- Reject with a clear error ("TokenFirewall already initialized"), or
- Update the existing `BudgetManager` limit rather than replacing the instance (preserving `totalSpent`), or
- Clearly document in the README that re-initialization resets the spent counter.
Description
In `src/interceptors/fetchInterceptor.ts`, `patchGlobalFetch` uses a module-level `isPatched` flag to prevent double-patching. However, `setBudgetManager` and `setModelRouter` are separate functions that update module-level variables after the patch:
```typescript
let isPatched = false;
let budgetManager: BudgetManager | null = null;
export function patchGlobalFetch(): void {
if (isPatched) {
return; // subsequent calls are no-ops
}
// patches globalThis.fetch once
isPatched = true;
}
export function setBudgetManager(manager: BudgetManager | null): void {
budgetManager = manager; // replaces previous manager silently
}
```
Impact
When an application calls `budgetGuard` twice (e.g. to update the monthly limit mid-month), the second call replaces `budgetManager` with a fresh instance that has `totalSpent = 0`. Because `isPatched = true`, the interceptor is already in place but now points to the new manager — effectively resetting the budget counter without any warning.
```typescript
budgetGuard({ monthlyLimit: 10, mode: 'block' });
// ... spend $9
budgetGuard({ monthlyLimit: 10, mode: 'block' }); // called again
// totalSpent resets to $0 in the new BudgetManager
// previous $9 spent is lost
```
Additionally, `unpatchGlobalFetch` (if it exists) must be called explicitly or `globalThis.fetch` is permanently patched for the process lifetime with no way to restore the original behavior.
Expected Behavior
Calling `budgetGuard` a second time should either: