75 lines
2.4 KiB
TypeScript
75 lines
2.4 KiB
TypeScript
/**
|
|
* Auto-index guardrail thresholds and pure decision logic (RFC #883 §7).
|
|
*
|
|
* The guardrail fires when a user is about to commit to a bulk indexing
|
|
* action (curated tier change, large multi-file upload, etc.) that would
|
|
* use a substantial amount of disk for embedding storage. It's a one-time
|
|
* confirmation step at scary thresholds — it doesn't fire for ordinary
|
|
* everyday operations. After the user confirms once for a given batch
|
|
* the action proceeds as it would have without the guardrail.
|
|
*
|
|
* Thresholds are intentionally conservative to avoid surprise consumption
|
|
* of a user's storage. Tweak both constants if the field experience
|
|
* suggests we're nagging users too aggressively.
|
|
*/
|
|
|
|
/** Absolute upper bound: estimates at or above this trip the guardrail. */
|
|
export const GUARDRAIL_ABSOLUTE_BYTES = 50 * 1024 * 1024 * 1024 // 50 GB
|
|
|
|
/** Relative-to-free-disk bound: estimates >= 10% of free disk trip too. */
|
|
export const GUARDRAIL_FREE_DISK_RATIO = 0.1
|
|
|
|
export type GuardrailReason =
|
|
| {
|
|
kind: 'over_absolute'
|
|
estimateBytes: number
|
|
thresholdBytes: number
|
|
}
|
|
| {
|
|
kind: 'over_free_disk'
|
|
estimateBytes: number
|
|
freeBytes: number
|
|
thresholdBytes: number
|
|
}
|
|
|
|
export type GuardrailVerdict = {
|
|
trips: boolean
|
|
reasons: GuardrailReason[]
|
|
}
|
|
|
|
/**
|
|
* Decide whether a bulk indexing action should be gated behind the
|
|
* guardrail modal. Caller passes the precomputed embedding-storage
|
|
* estimate (from `KbRatioRegistry.estimateBatch` in #891 / #897) and
|
|
* the free-disk figure from system info. Pass `freeBytes = 0` to skip
|
|
* the relative-disk check when free space isn't known.
|
|
*/
|
|
export function evaluateGuardrail(input: {
|
|
estimateBytes: number
|
|
freeBytes: number
|
|
}): GuardrailVerdict {
|
|
const reasons: GuardrailReason[] = []
|
|
|
|
if (input.estimateBytes >= GUARDRAIL_ABSOLUTE_BYTES) {
|
|
reasons.push({
|
|
kind: 'over_absolute',
|
|
estimateBytes: input.estimateBytes,
|
|
thresholdBytes: GUARDRAIL_ABSOLUTE_BYTES,
|
|
})
|
|
}
|
|
|
|
if (input.freeBytes > 0) {
|
|
const relativeThreshold = input.freeBytes * GUARDRAIL_FREE_DISK_RATIO
|
|
if (input.estimateBytes >= relativeThreshold) {
|
|
reasons.push({
|
|
kind: 'over_free_disk',
|
|
estimateBytes: input.estimateBytes,
|
|
freeBytes: input.freeBytes,
|
|
thresholdBytes: relativeThreshold,
|
|
})
|
|
}
|
|
}
|
|
|
|
return { trips: reasons.length > 0, reasons }
|
|
}
|