-
Notifications
You must be signed in to change notification settings - Fork 0
The Guardrail Engine
The engine lives in src/regrails/guardrail.py. It is the part of RegRails that decides — deterministically, before any LLM call. Given a typed ConsultationRequest and the encoded rules, decide() returns a GuardrailDecision carrying an outcome, a list of CFR citations, a risk tier, and a human_gate_required flag.
This page covers how decide() works, the seven outcomes and when each fires, the risk tiers and the human-gate rule, the FERPA-vs-Title-IV dispatch, and the tag-based intercept logic.
See also: Architecture for where the engine sits in the data flow, Frameworks-and-Rules for the rules it consults.
decide(req, rules) does four things, in order:
-
Dispatch by
topic. TheConsultationRequest.topicfield selects the regulatory domain:-
other→ short-circuits toout_of_scope(no rule applies;framework = "n/a"). -
aid_status→ the Title IV cascade (framework = "Title IV"). -
disclosure/unknown→ the FERPA cascade (framework = "FERPA").
-
-
Walk the cascade. The selected cascade is a fixed, ordered sequence of
ifchecks (a "first match wins" ladder). It returns a(outcome, citations, response_lines)tuple. The first matching rung decides; nothing below it runs. -
Assign risk tier + human gate.
_risk_for(outcome, framework, citations)maps the outcome to a(risk_tier, human_gate_required)pair (the rule is below). -
Build the decision.
decide()assembles aGuardrailDecisionwith a fresh UUID, the query, framework, timestamp, the matched-rule ids, the outcome, the risk tier, the gate flag, the citations, the rendered response lines (the deterministic rationale, not an LLM output), and a measuredlatency_ms. If an audit sink is supplied, it emits aGUARDRAIL_CONSULTEDevent plus an outcome-specific event into the hash-chained log.
The decision is 100% deterministic and reproducible without any model. The LLM (in llm.py) only runs afterward to phrase the user-facing reply, and it cannot alter the outcome.
The Outcome literal set is defined in models.py:
| Outcome | Meaning | Fires when (representative trigger) |
|---|---|---|
allow |
The action may proceed, subject to any cited obligation. | A FERPA exception is satisfied (emergency, parent audit-log inspection, verified studies agreement, verified vendor-as-school-official, financial-aid disclosure, de-identified release, or consent on file); or a Title IV informational question (SAP explainer, warning/probation status). |
block |
The action is prohibited; a remediation path is cited. | FERPA redisclosure without consent (§ 99.33); SSN combined with a directory request (§ 99.37(e)); a directory request for a student who opted out (§ 99.37); or the default deny when no § 99.31 exception applies and no consent is on file (§ 99.30). |
escalate_consent |
Needs written consent or a contractual condition first. | The studies exception's four cumulative conditions are not all on file (§ 99.31(a)(6)); or the outsourced-vendor safe-harbor's three conditions are not all met (§ 99.31(a)(1)(i)(B)). |
escalate_directory_check |
Needs a per-student opt-out lookup first. | A directory-information request where the student's opt-out status has not been confirmed (§ 99.37). |
escalate_human_review |
Irreversible / high-stakes — mandatory human gate. | Title IV: the student is in default (§ 668.32(g)(1)); a failed SAP evaluation and an aid determination is requested (§ 668.34(a)(7)); or any other eligibility determination request (§ 668.32). |
insufficient_facts |
A regulated matter, but key facts are missing. | Title IV: an aid determination is requested but SAP status is unknown and default status is None (§ 668.34(a)(3)). |
out_of_scope |
Not a regulated student-data / aid matter. |
topic == "other" — e.g. "what time does the library close?" |
The web platform's recorded demo shows one representative scenario for each outcome end to end (query → outcome → risk → gate → citation).
Each outcome maps to a risk tier — low, medium, or high — derived from reversibility and blast radius. The mapping is implemented in _risk_for():
| Condition | Risk tier | human_gate_required |
|---|---|---|
outcome == "escalate_human_review" |
high | true |
outcome == "block" |
medium | false |
outcome in {escalate_consent, escalate_directory_check} |
medium | false |
outcome in {insufficient_facts, out_of_scope} |
low | false |
outcome == "allow" and framework == "Title IV"
|
medium | false |
outcome == "allow" and a citation contains 99.36 (health/safety emergency) |
medium | false |
outcome == "allow" (otherwise) |
low | false |
The governance stance encoded here:
- low / reversible → safe to automate. Explaining a rule, an out-of-scope question, an aggregate de-identified release.
- medium → automate with a cited obligation or a quick check. A consent defect, a directory opt-out lookup, a recordkeeping duty, or any Title IV informational answer (which always carries a "the institution decides specifics" caveat).
-
high / irreversible → mandatory human gate.
escalate_human_reviewis the only outcome that setshuman_gate_required = true. Loss of aid eligibility, loan default, and yes/no eligibility determinations all route here. The engine refuses to let the AI answer and directs the student to the financial-aid office (a human).
The human gate is therefore a hard, deterministic property of the outcome — not a probabilistic judgment and not something the LLM can override. In the Title IV encoding, § 668.34(a)(7) (SAP termination) and § 668.32(g)(1) (default) are the clauses that drive the high tier and force the gate.
This is a design stance, not a guarantee. Tiers are assigned per rule by the author; the engine enforces the stance deterministically, but it cannot promise every real-world high-stakes situation has been classified as high. A miscategorized rule would route a serious case as medium. See the methodology/limitations notes (
docs/METHODOLOGY.md§6–7).
The two cascades occupy disjoint logic and disjoint tag spaces, so a query is settled entirely within one framework.
Selected for topic disclosure or unknown. The ladder, in order (first match wins):
-
Emergency —
emergency_requesttag +emergency_justified→allow(§ 99.36 health/safety) with the § 99.32(a)(5) recordkeeping obligation surfaced. -
Redisclosure default-block —
redisclosure_requestwithout consent →block(§ 99.33). -
Parent / eligible-student audit-log inspection →
allow(§ 99.32(c)(1)). -
SSN combined with directory data →
block(§ 99.37(e) + § 99.30). -
Directory-information path — if the student opted out →
block(§ 99.37); otherwise →escalate_directory_check(confirm opt-out status first). -
Studies / research — if de-identified →
allow(§ 99.31(b)(1)); else if all four cumulative § 99.31(a)(6) conditions are on file →allow; otherwise →escalate_consentlisting the missing conditions. -
Vendor as school official — if all three cumulative § 99.31(a)(1)(i)(B) safe-harbor conditions are met →
allow; otherwise →escalate_consentlisting what's missing. -
Financial-aid disclosure (FERPA's § 99.31(a)(4) prong) →
allow. -
Consent on file →
allow(§ 99.30). -
Default block — nothing matched and no consent →
block(§ 99.30(a)). This is the deny-by-default backstop.
Selected for topic aid_status. The ladder, in order:
-
Loan default —
student_in_default is True→escalate_human_review/ high / gated (§ 668.32(g)(1)). -
Failed SAP + determination asked —
sap_status == "failed_eval"andaid_determination_requested→escalate_human_review/ high / gated (§ 668.34(a)(7)). -
Determination asked but SAP unknown and default unknown →
insufficient_facts(§ 668.34(a)(3)). -
Financial-aid warning —
sap_status == "on_warning"→allow, informational (§ 668.34(a)(8)(i)). -
Financial-aid probation —
sap_status == "on_probation"→allow, informational (§ 668.34(a)(8)(ii)); if an appeal basis is present, also cites § 668.34(a)(9)(ii). -
Any other eligibility determination requested →
escalate_human_review/ high / gated (§ 668.32). -
General informational question →
allow(§ 668.34 + § 668.32), with the "the financial-aid office makes specific determinations" caveat.
The asymmetry is deliberate: Title IV advice (explaining SAP, warning, probation) automates; Title IV determinations (default, SAP termination, yes/no eligibility) route to a human.
Routing inside each cascade is driven by deterministic tag derivation, not by reading the free-text query with a model. derive_tags(req) computes a set[str] of rule-trigger tags purely from the structured ConsultationRequest fields:
-
topic == "other"→ the empty set (no tags, soout_of_scope). -
topic == "aid_status"→ Title IV tags (aid_status_query,sap_question,aid_eligibility, plusaid_determination,sap_termination,loan_default,sap_appeal, etc., conditioned onsap_status,student_in_default,aid_determination_requested,appeal_basis_present, andpurpose). - FERPA path →
pii_request,default_consent_gate,disclosure, then role-derived tags (school_official→school_official_request;outsourced_vendor→vendor_request+ai_vendor;parent→parent_request;researcher→studies_request;emergency_responder→emergency_request;law_enforcement→subpoena_request;third_party→third_party_forward), purpose-derived tags, and data-derived tags.
Data items are classified against two fixed sets: a directory set (name, address, phone, major, dates of attendance, athletic-roster fields, etc.) and a non-directory set (GPA, transcript, test scores, SSN, disciplinary records, health records, etc.). A non-directory item raises non_directory_request; an SSN alongside other data raises ssn_combined; directory-only items raise directory_info_request.
Two consequences:
-
match_rules(req, rules)returns every encoded rule whosetriggersintersect the derived tags — that's thematched_ruleslist on the decision, the link back to the rule catalog. FERPA and Title IV tags never overlap, so the matcher never cross-matches frameworks. - The cascade ladders read these tags (and a few raw boolean fields like
consent_on_fileandemergency_justified) to pick the rung.
Because tag derivation is pure and total, the same ConsultationRequest always yields the same tags, the same matched rules, and the same outcome — which is exactly what the golden corpus asserts on. The honest tradeoff: a real query phrased outside the modeled tags can land on the wrong cascade, or fall through to the deny-by-default rung, for the wrong reason (see docs/METHODOLOGY.md §7, failure mode 2).