Runtime Invariants and Guardrails
Part IX β Production Debugging and Prevention
The guardrail that watched the wrong door
Chapter 53 ended with a contractual repair and a bidirectional test: scope-less tickets now reject-and-ask in CI. In production, the next scope-less ticket still double-spends. The postmortem reveals why: the guardrail checked citation format at the output, while the defect lived at the handoff between research and action β an unchecked edge the guardrail never evaluated. The team owned a passing test and an unguarded path. Validation without enforcement is a wish.
OBSERVATION: the regression test test-47 passes in CI on scope-less inputs; the production trace for the new incident shows no guardrail evaluation event on the researchβaction edge. HYPOTHESIS H1 (unenforced invariant): a validated check exists but is not wired into the live path. H2 (wrong-layer check): the guardrail evaluates but at a stage whose verdict cannot prevent the failure. H3 (threshold misfit): the guardrail evaluates at the right stage but its threshold admits the failing case. INFERENCE: none yet β H1/H2/H3 predict different production verdict records and separate only once guardrails log their evaluations per request.
This chapter’s question: which validated invariants earn runtime enforcement, at which production handoff, with what refuse/hedge/escalate policy β and at what measured threshold?
Why “add a safety prompt” fails first
The obvious move β appending “be careful, verify scope before authorizing” to the system prompt β fails because instructions are not enforcement. Six defects hide behind prompt-safety:
- No evaluation point. The instruction exists in text but no code checks compliance before the side effect. Behavior requested, nothing verified.
- Wrong-layer verdicts. The check runs after the money moves (output moderation on an executed refund). Detection is not prevention.
- Unmeasured thresholds. “Block low-confidence authorizations” with no measured score distribution. The threshold is folklore; the failure walks under it. Worse, confidence is often the wrong signal: Kang and colleagues built model assertions β arbitrary functions over a model’s input and output that flag likely errors β precisely because assertions catch high-confidence wrong outputs that uncertainty-based monitoring never sees (Kang et al., 2020). A scope-less authorization can be issued with total confidence; the guardrail must check the invariant (“one authorization per scope”), not the model’s certainty.
- Silent passes. Guardrails that log only trips. A guardrail with no per-request verdict record cannot distinguish “checked and passed” from “never ran” β exactly the H1/H2 ambiguity above.
- Blanket refusal. Every edge case refused, users route around the guardrail within a week. Uncalibrated strictness teaches circumvention.
- Prompt-only patching. The guardrail lives in wording that the next “conciseness” edit deletes (Ch30’s guardrail erosion, now with live consequences).
OPINION: a guardrail nobody can see evaluate is a scarecrow. Wire it, log its verdict, or admit the field is unguarded.
The mental model: production handoffs as checkpoints β every edge that can move money, expose data, or commit an irreversible act carries a machine-checked invariant with a refuse/hedge/escalate policy. Refuse blocks the act and asks; hedge completes partially with explicit limits stated to the user; escalate pauses for human review with the frozen bundle attached. The invariant is a model assertion (above); the checkpoint is a precondition on the committing operation in Meyer’s Design-by-Contract sense (Chapter 8) β the caller must satisfy it before the commit, and a violation is the caller’s fault, not the guardrail’s.
The three-action policy is selective prediction with an extra option. Geifman and El-Yaniv formalized the reject option for classifiers β abstain rather than predict below a chosen threshold β and showed how to pick that threshold so the risk on the covered cases stays under an accepted level, a risk-coverage tradeoff (Geifman & El-Yaniv, 2017). Refuse/hedge/escalate is that decision with the reject branch split three ways, and the threshold calibration below is the risk-coverage tradeoff done empirically. Thresholds are MEASUREMENT from calibration trials, marked as setup choices with the calibration set recorded.
The method: invariant placement with calibrated policies
Promote validated invariants to runtime in four steps:
- Enumerate guarded handoffs. List every edge whose traversal commits something: authorizations, sends, deletes, publishes, escalations. Each gets a named invariant (e.g., “one authorization per (ticket, scope) pair”) β written from the spec or mined from pre-incident traffic (Chapter 8) β the clause it enforces, and the evaluation point before the commit. Unlisted edges are explicitly UNGUARDED β declared, not discovered mid-incident.
- Assign refuse/hedge/escalate per edge. Irreversible + high blast radius β refuse-and-ask. Partially completable β hedge with stated limits. Ambiguous but consequential β escalate with the bundle attached. The choice is recorded per edge; improvisation at incident time is a process failure.
- Calibrate thresholds by MEASUREMENT. Run the guardrail over a frozen calibration set (pinned inputs with known good/bad labels from Chapter 53 bundles plus clean traffic), β₯3 trials per item. Record the pass/trip distribution; set the threshold where the team accepts the measured tradeoff. The threshold number, the calibration-set hash, and the trial count ship with the guardrail β all marked as setup choices, changeable when traffic shifts.
- Log every verdict. Each request carries per-guardrail verdict events (evaluated/passed/tripped + threshold + clause version) in the Chapter 52 record. A missing verdict event means UNEVALUATED β never “presumed passed.”
flowchart TD
E["enumerate every edge that commits: authorize / send / delete / publish / escalate"] --> RG["register per edge: a named invariant + refuse / hedge / escalate policy + an evaluation point BEFORE the commit"]
RG --> CAL["calibrate the threshold on a frozen set (known-bad + known-good + near-miss), >=3 trials: trips on bad, passes on good"]
CAL --> WIRE["wire the checkpoint pre-commit; emit a verdict event per request (evaluated / passed / tripped + clause version)"]
WIRE --> INC{"on incident: verdict event for this edge?"}
INC -->|"absent"| H1["H1 unenforced β a validated check exists but is not wired into the live path"]
INC -->|"present, but after the commit"| H2["H2 wrong-layer β detection, not prevention; relocate the checkpoint"]
INC -->|"present pre-commit, failing case passed"| H3["H3 threshold misfit β recalibrate on a refreshed set"]
GUARDRAIL CLAUSE (registered per edge):
edge: research->action | invariant: one authorization per (ticket, scope)
policy: REFUSE-and-ask on scope absent | clause v3 | calibrated on set c-19 (n=___)
threshold: trip when scope_id missing OR pair count > authorizations (measured ___/___ trips on bad, ___/___ passes on good, x3 trials)
verdict event per request: evaluated / passed / tripped (+ clause version)
RULE: no verdict event means UNEVALUATED. Silence never counts as a pass.
OBSERVATION (constructed illustration, not a measured run): clause v3 evaluated on the incident request would have tripped (scope absent) under the calibrated threshold; the production record shows no verdict event β H1-shaped, not H3-shaped. UPDATED BELIEF: H1 supported for this instance (unenforced invariant on this edge); H2 supported as compounding cause (the existing format check sits downstream of the commit); H3 exonerated here (threshold trips correctly on calibration). Wire the clause, move the checkpoint β two repairs, ordered.
No prompt wording (“the model was told to be careful”), no trip count without a denominator, no agreement between reviewers that “this looks safe,” and no downstream quiet substitutes for per-request verdict events with calibrated thresholds.
Example: wiring the scope checkpoint before the commit
The practitioner places the invariant where it can still prevent, then proves the placement:
# guardrail wiring: checkpoint before commit, verdicts always logged
@checkpoint(edge="research->action", clause="scope-auth-v3", policy="refuse-and-ask")
def authorize(ctx):
verdict = check_scope_pair(ctx.ticket, ctx.scope, ctx.authorizations) # MEASUREMENT
log_verdict(request_id(ctx), clause="scope-auth-v3", verdict=verdict) # always emitted
if verdict == "trip":
return refuse_and_ask(ctx, frozen_bundle(ctx)) # no commit on trip, ever
return proceed(ctx)
# Calibration (proposed): frozen set c-19, >=3 trials/item, threshold from distributions.
# Predictions pre-written: bad inputs trip ___/___; good inputs pass ___/___.
In the constructed case calibration over the frozen set trips on all scope-less authorizations across three trials while passing legitimate multi-authorization requests (distinct scopes, distinct pairs) β the hedge/escalate distinction doing real work: malformed requests refuse, ambiguous-but-shaped requests escalate with bundles attached, clean requests pass untouched. Production verdict events now show evaluated-per-request; the next scope-less ticket halts-and-asks instead of double-spending. The licensed claim covers this edge under clause v3 and calibration set c-19 β not authorization safety in general.
Research lineage: assertions, reject options, and model-based guards
A guardrail can itself be a model, with its own error model. The deployed pattern for content and policy guardrails is a classifier or LLM safeguard at the input and output β Inan and colleagues’ Llama Guard is the canonical example (Inan et al., 2023), and NeMo Guardrails and Guardrails AI are frameworks for wiring such checks. For a structural invariant like “one authorization per (ticket, scope),” a deterministic check is preferable β it has no error model. Where the invariant is semantic (is this refund request fraudulent?), the guardrail is a model, and its calibration set, threshold, and error rates get the same Chapter 16 scrutiny as any other model.
The verdict event is the assertion’s firing record. Kang and colleagues use model-assertion firings not just to block but to sample β the flagged cases become training data, reducing labeling cost by up to 33% versus uncertainty-based active learning in their experiments (Kang et al., 2020). The per-request verdict events this chapter mandates are the same asset: a stream of “the invariant almost fired here” that feeds the calibration set and the Chapter 53 conveyor.
The invariant need not be hand-written. Chapter 8’s second move β mine the contract from the passing runs, Daikon-style likely-invariant inference, rather than only asserting it from the spec β applies directly at the committing edge. A relation that held across thousands of pre-incident authorizations (“a scope_id is present whenever an authorization commits”) is a candidate clause. Calibration is what promotes it: the mined candidate must trip on the Chapter 53 bad-cases and pass clean traffic before it is wired. Mined or written, the enforced clause carries a version and a calibration-set hash β a runtime guardrail is Chapter 8’s assertion with the predicate learned instead of typed.
Refuse defaults to no-commit, always. On a trip, the operation does not proceed β this is Saltzer and Schroeder’s fail-safe default (Chapter 51) at the committing edge. A guardrail that fails open on its own error is worse than no guardrail, because it launders the commit as checked.
Lab 54: guardrail calibration with pre-written trip predictions (proposed)
PROPOSED, not executed: no author-measured results are reported. The evidence this chapter requires is the reader’s own calibration set.
Setup. Pick one production handoff you own that can commit an act. Assemble a frozen calibration set: known-bad inputs (from Chapter 53 bundles) plus known-good inputs (including near-miss legitimate cases). Freeze pins. The threshold setting is the independent variable; set, task, and environment are controlled.
Task.
- Before running, write H1/H2/H3 with distinct predicted verdict signatures: H1: “guardrail unwired (no verdict events in production sample)”; H2: “guardrail mislayered (verdicts exist but post-commit)”; H3: “threshold misfit (verdicts pre-commit but failing cases pass).”
- Run each calibration item β₯3 trials through the guardrail; record trip/pass distributions verbatim; set the threshold from the measured tradeoff and record the set hash with it.
- Sample live traffic post-wiring and verify per-request verdict events exist pre-commit.
| Hypothesis | Predicted verdict signature | FORECAST | OBSERVATION (Γ3 trials) | UPDATED BELIEF |
|---|---|---|---|---|
| H1 unwired | verdict events absent | ___ | ___ | live/exonerated |
| H2 mislayered | verdicts post-commit | ___ | ___ | live/exonerated |
| H3 misfit | failing cases pass | ___ | ___ | live/exonerated |
Success criterion. A registered clause with policy, a calibration table with distributions, per-request verdict events in live traffic, and the threshold + set hash recorded as setup choices. A safety sentence added to a prompt is explicitly not completion.
Companion tool: Runtime Guardrail Checklist
What it accepts: the handoff inventory with per-edge policies, clause definitions with versions, the frozen calibration set with labels, trial verdict distributions, and a sample of production records with verdict events. What it performs: it verifies every committing edge has a registered clause evaluated pre-commit, checks thresholds against the calibration distributions (trips on bad, passes on good, near-misses handled by policy), confirms per-request verdict events in live samples, and flags unwired, mislayered, or misfit edges with the deciding records cited. What it can establish: whether each committing edge is guarded pre-commit at its calibrated threshold β for the examined edges and calibration set only. What it cannot establish: semantic completeness (unknown failure classes need no threshold β they need Chapters 52β53), threshold optimality across traffic shifts, or safety of unlisted edges. It never treats prompt wording, tripless trip counts, reviewer agreement, single-trial calibrations, or downstream quiet as guardrail evidence. How its output changes your next action: unwired routes to wiring before the commit; mislayered routes to checkpoint relocation; misfit routes to threshold recalibration on a refreshed set; all-guarded routes to Chapter 55’s cost/latency budgets β each as one intervention with pre-written predictions.
Paper form, sufficient for this chapter:
Edges: ___ committing, ___ guarded pre-commit, ___ UNGUARDED (named)
Clause ___ v___: policy refuse/hedge/escalate ___ | cal set ___ (n=___)
Bad trip ___/___ x3 | good pass ___/___ x3 | near-miss policy ___
Live verdicts: present pre-commit ___/___ sampled NEXT: wire / relocate / recalibrate
Where a software implementation does not yet exist in the reader’s stack, this record is the tool. Checkpoints before commits.
Reusable procedure: guard every committing edge
- Inventory commits β every edge that moves money, data, or state, named; the rest declared UNGUARDED.
- Register policy β refuse/hedge/escalate per edge, pre-registered, versioned.
- Calibrate by MEASUREMENT β frozen set, β₯3 trials, threshold + set hash shipped together.
- Evaluate pre-commit β verdict before the act, always logged.
- Audit live verdicts β sample production records; silence means UNEVALUATED.
Failure modes
- Prompt-as-guardrail. Safety wording with no evaluation point. Requests, not enforcement.
- Post-commit checkpoints. Verdicts after the money moves. Autopsy labeled prevention.
- Folklore thresholds. Numbers from intuition. Failures walk under unmeasured lines.
- Silent passes. Trips logged, passes invisible. “Never ran” indistinguishable from “passed.”
- Blanket refusal. Strictness without calibration. Users route around it; the guardrail becomes decoration.
- Guardrail erosion. Wording edits deleting constraints. Version clauses like code (Ch30) or lose them.
- Simultaneous rewiring. All edges re-guarded after one incident. One edge, one clause, pre-written prediction.
- Unknown-class complacency. Calibrated guardrails cited against never-seen failures. Guardrails enforce the known; observability catches the rest.
Limits, per contract: one checklist covers the examined edges, clause versions, and calibration set; it does not certify unknown-class safety, does not transfer thresholds across traffic regimes, and stays UNKNOWN where verdict events are absent or post-commit.
References
- Daniel Kang, Deepti Raghavan, Peter Bailis, and Matei Zaharia. Model Assertions for Monitoring and Improving ML Models. Proceedings of Machine Learning and Systems (MLSys), 2020. https://proceedings.mlsys.org/paper_files/paper/2020/hash/e851ca7b43815718fbbac8afb2246bf8-Abstract.html
- Yonatan Geifman and Ran El-Yaniv. Selective Classification for Deep Neural Networks. Advances in Neural Information Processing Systems 30 (NeurIPS), 2017. https://arxiv.org/abs/1705.08500
- Hakan Inan, Kartikeya Upasani, Jianfeng Chi, Rashi Rungta, Krithika Iyer, et al. Llama Guard: LLM-Based Input-Output Safeguard for Human-AI Conversations. arXiv:2312.06674, 2023. https://arxiv.org/abs/2312.06674
- Bertrand Meyer. Applying “Design by Contract”. Computer 25(10), 1992, pp. 40β51. https://doi.org/10.1109/2.161279
Debugging Checklist
- All committing edges inventoried (unguarded ones named, not hidden)?
- Per-edge refuse/hedge/escalate policy pre-registered and versioned?
- Calibration set frozen with known-bad, known-good, and near-miss cases?
- H1/H2/H3 verdict predictions pre-written with distinct signatures?
- Each calibration item run β₯3 trials with distributions recorded?
- Threshold shipped with set hash and trial count as setup choices?
- Live production sample shows pre-commit verdict events per request?
- No wording, tripless counts, agreement, single trials, or quiet cited as safety?
What This Chapter Established
- Runtime invariants as pre-commit checkpoints with refuse/hedge/escalate policies and MEASUREMENT-calibrated thresholds β demonstrated on the constructed scope-authorization edge, no measured runs claimed.
- The unwired/mislayered/misfit separation (H1/H2/H3) via per-request verdict events, plus the silence-means-UNEVALUATED rule.
- Lab 54 as a proposed guardrail calibration the reader executes; the Runtime Guardrail Checklist contract (accepts/performs/can-establish/cannot-establish/next-action).
- What was NOT proved: any completeness claim over unknown failure classes, any threshold optimality across traffic, or any safety certification. Known classes enforced; nothing universal.
- Research grounding: the runtime invariant is a model assertion (Kang et al.), which catches high-confidence wrong outputs that confidence-thresholding misses and whose firings feed active learning (up to 33% lower labeling cost vs. uncertainty sampling); the clause can be hand-written or mined from passing traffic (Daikon-style, Chapter 8); the checkpoint is a Design-by-Contract precondition (Meyer); refuse/hedge/escalate is a selective-prediction reject option calibrated by the risk-coverage tradeoff (Geifman & El-Yaniv); semantic guardrails are themselves models with their own error model (Llama Guard) and get Chapter 16 scrutiny; refuse defaults to no-commit (fail-safe default, Ch 51).
- Position in the arc: Chapter 52 emits the evidence, 53 conveys it to durable tests; this chapter enforces validated checks while users are still in the loop. Fail loudly before users pay.
Next
The system now refuses known-bad acts before they commit. But refusal is not the only way production AI fails its users: the next incident is not a wrong answer but a $40,000 inference bill and a 90-second timeout β quantities nobody instrumented as debuggable. Chapter 55, “Debugging Cost and Latency,” attributes spend and delay per stage; which stage burns the budget is its chapter’s to establish, not this one’s.