Tenant Quota
Per-tenant rate / byte / row quotas with enforcement helpers and audit emission on breach. Multi-tenant deployments need three things the framework's DB layer doesn't natively provide:
1. Storage caps — refuse INSERT when a tenant has consumed more than its allowance (defaultBytesCap, or a perTenantBytesCap[tenantId] override). 2. Query budgets — refuse SELECT when a tenant exceeds its rolling-window QPS or rows-read totals. 3. Isolation — every row a query reads under a claimed tenantId MUST belong to that tenant. Cross-tenant rows surface as db.tenant.crossover audit events.
Replaces the global maxRowsPerQuery knob for tenant-scoped scenarios — operators were previously forced to pick one global cap that would starve large tenants or under-cap small ones.
Storage-cap accounting: bytesUsed is computed by walking every table whose schema declares the configured tenantField and summing the textual length of every column for matching rows. The framework caches the per-tenant total for cacheTtlMs (default 30s) so a hot path doesn't pay the scan on every assert.
Query budget: sliding-window counter keyed (tenantId, windowStart). Window defaults to 60s. observe() rejects when either the QPS-equivalent call count exceeds perTenantQpsCap * window or the rows-read total exceeds perTenantTotalRowsRead.
Audit emissions: - tenant.quota.exceeded — assert() refused an insert/update - tenant.budget.exceeded — observe() refused a query - db.tenant.crossover — instrumentQuery saw rows belonging to the wrong tenant under the operator-claimed tenantId
SOC 2 CC6.1 ("logical access controls") + ISO 27001 A.8.1.5 ("classification of information") map directly onto this primitive — operators wire its emissions into the same audit chain auditors read.
b.tenantQuota.create(opts) #
{
{
db: object, // required, b.db namespace
tenantField: string, // required, e.g. "tenantId"
defaultBytesCap?: number, // default: 1 GiB (C.BYTES.gib(1))
perTenantBytesCap?: { [tenantId: string]: number },
tables?: string[], // override auto-detection
audit?: boolean, // default: true
cacheTtlMs?: number, // default: 30_000
}
}
Build a per-tenant storage-cap enforcer. Returns an object exposing assert(tenantId) (throws TenantQuotaError on breach), snapshot(tenantId) (returns { tenantId, bytesUsed, bytesCap, percent }), list() (snapshot every distinct tenant), and invalidate(tenantId?) (drop the per-tenant cache so the next assert recomputes). The cache TTL trades freshness for cost on the hot path; bump it down for stricter limits.
var quota = b.tenantQuota.create({
db: b.db,
tenantField: "tenantId",
defaultBytesCap: b.constants.BYTES.gib(1),
perTenantBytesCap: { "tenant-vip": b.constants.BYTES.gib(10) },
});
await quota.assert("tenant-acme");
// → { tenantId: "tenant-acme", bytesUsed: 12345, bytesCap: 1073741824, percent: 0.0000115 }
b.tenantQuota.budget(opts) #
{
{
db: object, // required, b.db namespace
tenantField: string, // required
perTenantQpsCap?: number, // default: 100 calls/sec
perTenantTotalRowsRead?: number, // default: 50_000 rows per window
window?: number, // default: 60_000 ms (C.TIME.minutes(1))
audit?: boolean, // default: true
}
}
Build a per-tenant query-budget enforcer. Returns an object exposing observe(tenantId, info) (throws TenantQuotaError on breach), snapshot(tenantId) (returns the current window's counters), and reset(tenantId?) (drop counters). Sliding-window: every breach past the configured QPS or rows-read total emits tenant.budget.exceeded and refuses the call.
var budget = b.tenantQuota.budget({
db: b.db,
tenantField: "tenantId",
perTenantQpsCap: 100,
perTenantTotalRowsRead: 50000,
window: b.constants.TIME.minutes(1),
});
var snap = budget.observe("tenant-acme", { rowsRead: 12 });
// → { calls: 1, rowsRead: 12, windowMs: 60000 }
b.tenantQuota.instrumentQuery(opts) #
{
{
rows: object[], // required, the query result rows
tenantField: string, // required, e.g. "tenantId"
tenantId: string, // required, the operator-claimed tenant
table?: string, // optional, recorded in the audit metadata
audit?: boolean, // default: true
}
}
Walk a result set and detect rows whose tenantField value disagrees with the operator-claimed tenantId — a multi-tenant isolation breach. Returns { ok, crossover } where crossover is the list of offending row indexes + their actual tenantId values. Audit emission db.tenant.crossover fires with a five-row sample when any breach is detected so the framework's chain-signed audit carries the forensic trail without dumping the whole result set.
var rows = [
{ _id: 1, tenantId: "tenant-acme", name: "ok" },
{ _id: 2, tenantId: "tenant-other", name: "leak" },
];
var result = b.tenantQuota.instrumentQuery({
rows: rows,
tenantField: "tenantId",
tenantId: "tenant-acme",
table: "orders",
});
// → { ok: false, crossover: [{ index: 1, actualTenantId: "tenant-other" }] }
Last updated 2026-08-08T16:39:49.652Z by seeder.