Initial Commit

This commit is contained in:
2026-05-27 23:29:53 +01:00
commit fbc70f96e6
10000 changed files with 1102152 additions and 0 deletions

View File

@@ -0,0 +1,108 @@
# `budget` — `BudgetTracker`
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
This module owns the per-request resource-budget enforcement for the
sandboxed tier. Sampling primitives (CPU percentage, resident memory,
wall-clock progress) are supplied by the worker host because they live
outside this package's dependency budget; this module aggregates
samples against the policy's `budgets` block and raises
`SandboxPermissionError(code: SAND_PERM.BUDGET_EXCEEDED)` on the first
newly-breached dimension.
The tracker is idempotent across repeat samples on the SAME dimension:
once a dimension has breached, subsequent ticks against that dimension
do NOT re-emit the deny counter or re-throw — the dimension is
considered "tripped" until the tracker is discarded at request end.
## Features
- `BudgetTracker.tick(sample)` — consumes one `{ cpuPercent, memoryMb }`
sample plus the wall-clock from the injected `Clock`.
- `BudgetTracker.elapsedMs()` — milliseconds elapsed since construction.
- Idempotent breaches — `cpuPercent`, `memoryMb`, `wallClockMs` each
emit at most once per tracker instance.
- Throws `SandboxPermissionError` with `code === SAND_PERM.BUDGET_EXCEEDED`
on the first newly-breached dimension and increments
`plugin.budget_exceeded_total{plugin, budget}`.
## Usage: Basic
```ts
import { BudgetTracker } from "@suaveplan/api-sandbox-permissions/budget";
import { createPermissionInstruments } from "@suaveplan/api-sandbox-permissions/instruments";
import { PermissionPolicy } from "@suaveplan/api-sandbox-permissions/policy";
const policy = new PermissionPolicy({
permissions: { budgets: { cpuPercent: 50, memoryMb: 256, wallClockMs: 30_000 } },
});
const instruments = createPermissionInstruments({ plugin: "billing" });
const clock = { now: () => Date.now() };
const tracker = new BudgetTracker({ policy, instruments, clock });
tracker.tick({ cpuPercent: 10, memoryMb: 64 }); // within budget — no throw
```
## Usage: Advanced
```ts
import { BudgetTracker } from "@suaveplan/api-sandbox-permissions/budget";
import { createPermissionInstruments } from "@suaveplan/api-sandbox-permissions/instruments";
import { PermissionPolicy } from "@suaveplan/api-sandbox-permissions/policy";
import { SAND_PERM_CODES, SandboxPermissionError } from "@suaveplan/api-sandbox-permissions/error-codes";
const policy = new PermissionPolicy({
permissions: { budgets: { wallClockMs: 30_000 } },
});
const instruments = createPermissionInstruments({ plugin: "billing" });
// Injectable Clock — drive the wall-clock breach deterministically.
let t = 0;
const clock = { now: () => t };
const tracker = new BudgetTracker({ policy, instruments, clock });
t = 30_001;
try {
tracker.tick({ cpuPercent: 0, memoryMb: 0 });
} catch (err) {
if (err instanceof SandboxPermissionError) {
console.assert(err.code === SAND_PERM_CODES.BUDGET_EXCEEDED);
// err.context = { capability: "budget", reason: "wallClockMs" }
}
}
// Subsequent ticks against the same dimension are no-ops — the
// dimension is already tripped.
t = 35_000;
tracker.tick({ cpuPercent: 0, memoryMb: 0 }); // returns silently
```
## API Reference
- `interface BudgetSample { cpuPercent: number; memoryMb: number; }`
- `interface BudgetTrackerOptions { policy; instruments; clock: Clock; }`
- `type BudgetBreach = "cpuPercent" | "memoryMb" | "wallClockMs"`
- `class BudgetTracker`
- `constructor(options)`
- `tick(sample): void` — throws on first newly-breached dimension.
- `elapsedMs(): number`
- `readonly policy: PermissionPolicy`
- `readonly instruments: PermissionInstruments`
## Implementation Notes
The `Clock` is injected per `@suaveplan/types` — never pulled from
`Date.now()` directly so test code can drive a deterministic timeline
without `vi.useFakeTimers()`. The constructor records `startedAt = clock.now()`
once; every subsequent `elapsedMs()` call subtracts from a fresh
`clock.now()` so wall-clock progress is monotonic with the clock.
`evaluate` is private and centralises the breach record so all three
dimensions go through the SAME counter + throw codepath; this guarantees
the conformance harness's "deny counter increments exactly once per
breach" invariant holds for every dimension without per-dimension
duplication.