One-time confirmation step gating bulk indexing actions that would
consume a substantial amount of disk for embedding storage. Fires only
when the user has policy=Always (i.e., the system would auto-index)
AND the estimate trips either:
- GUARDRAIL_ABSOLUTE_BYTES = 50 GB embedding cost, OR
- GUARDRAIL_FREE_DISK_RATIO = 10% of current free disk space
Under policy=Manual the guardrail is silent because the user has
already opted out of automatic ingestion — the files would just queue
as pending_decision either way.
Pieces
- inertia/lib/kb_guardrail.ts: pure decision helper with two constants
and an evaluateGuardrail() that returns a verdict + reasons. No I/O
on the helper itself so the logic is trivially testable
- inertia/components/KbGuardrailModal.tsx: confirmation dialog. Headless
UI Transition + Dialog, amber 'large operation' header, plain-English
estimate summary, [Cancel] / [Proceed anyway] footer. z-[60] so it
layers above the tier modal underneath instead of replacing it
- inertia/components/TierSelectionModal.tsx integration: handleSubmit
now evaluates the guardrail when policy=Always and embedEstimate is
available; if it trips, we stash the verdict in state and render the
guardrail modal as an overlay. Confirm runs finalizeSubmit (which is
the pre-existing onSelectTier + onClose path); Cancel just closes the
guardrail and leaves the tier modal as-is so the user can change
their tier choice or flip the policy
The disk-free signal comes from the existing useSystemInfo hook +
getPrimaryDiskInfo helper. Passing freeBytes=0 (unknown) skips the
relative-disk check, so the modal still works on hosts whose disk
introspection failed — just relies on the absolute 50 GB threshold
Tests
- 9 cases in tests/unit/kb_guardrail.spec.ts: standard small batch (no
trip), exact absolute threshold trips, over-absolute trips, over 10%
free trips, both-at-once trips with two reasons, freeBytes=0 skip,
freeBytes=0 + over-absolute trip, exact-10% boundary trips, just-
under-both safe. All green.
Stacks on feat/kb-tier-estimate-on-disk (#897) — consumes that PR's
estimate endpoint to compute the verdict input. Auto-rebases to rc
when #897 merges.
Pairs with #894 (policy toggle) and #899 (JIT prompt): together the
three PRs cover the 'how do I avoid surprising the user with auto-
indexing they didn't ask for?' arc.
Out of scope (deferred)
- 6 hr time threshold (RFC §7): needs a per-host chunks-per-second
metric we don't capture yet; would be a follow-up after Phase 4
self-calibration (RFC §15) lands
- Wider integration (KbPolicyPromptBanner 'Index now' button, manual
KB-modal sync): TierSelectionModal is the dominant bulk-decision
surface and the right place to land this first