DETERMINISTIC, MIXED, or PARTIAL / HEURISTIC to indicate the strength of the underlying check.
DETERMINISTICguards return reproducible, provable results for supported, structured inputs.MIXEDguards run a deterministic computation (date arithmetic, Z3 SAT/UNSAT) over parsed inputs. The computation is provable; the parsed lookup that feeds it is not authority proof.PARTIAL / HEURISTICguards apply structural or rule-based checks. A passing result does not prove that the underlying legal claim is correct — only that it matched a supported pattern.
Verification traces and evidence types
As of v0.4.0, every guard returns averification_trace — an ordered list of VerificationStep records. Each step is tagged with an evidence_type, and VerificationStep.is_proven() returns True only for DETERMINISTIC steps.
evidence_type | Meaning | is_proven() |
|---|---|---|
DETERMINISTIC | Proven by math/logic (Z3, date arithmetic, exact compare) | True |
PARSED | Read/matched from structure or lookup — not authority proof | False |
INFERRED | Pattern/keyword derived — may be wrong on edge cases | False |
HEURISTIC | Approximate/statistical signal | False |
UNSUPPORTED | Guard cannot model this input — fail-closed | False |
trace_to_dict() to export a trace into audit logs. Non-serializable input values are stringified (no silent data loss).
1. DeadlineGuard
Status:DETERMINISTIC
Purpose: Verify date calculations in contracts for structured, unambiguous inputs.
The problem
LLMs frequently miscalculate deadlines:- Confuse business days vs calendar days
- Ignore leap years
- Forget jurisdiction-specific holidays
The solution
Parameters
The date the contract was signed (ISO format or natural language).
The term description (e.g., “30 days”, “30 business days”, “2 weeks”, “3 months”, “1 year”).
The deadline claimed by the LLM.
Allow +/- this many days when verifying the deadline. Useful for accommodating minor rounding differences.
Response fields
| Field | Type | Description |
|---|---|---|
verified | bool | Whether the claimed deadline matches the computed deadline. Always False when is_computable is False. |
signing_date | datetime | Parsed signing date |
claimed_deadline | datetime | The deadline claimed by the LLM |
computed_deadline | Optional[datetime] | The correct deadline computed by the guard. None when the term is ambiguous and the guard fails closed. |
term_parsed | str | The original term string, or "ERROR" when date parsing fails |
difference_days | Optional[int] | Absolute difference in days between claimed and computed. None when no deadline could be computed. |
message | str | Human-readable verification message. Starts with ⚠️ UNVERIFIABLE when the term is ambiguous. |
is_computable | bool | True when the term parsed into an explicit quantity and unit. False when the input was ambiguous, unparseable, or the dates were invalid. |
verification_mode | str | Always "SYMBOLIC" for legal verification |
Fail-closed behavior on ambiguous terms
DeadlineGuard does not invent deadlines from vague legal language. If the term cannot be parsed into a deterministic (quantity, unit) pair, the guard returns a fail-closed result with verified=False, is_computable=False, and computed_deadline=None.
A term is treated as UNVERIFIABLE when either:
- It contains no numeric quantity (e.g.,
"forthwith","promptly after notice","within a reasonable period","as soon as practicable","without undue delay"). - It contains a number but no recognized time unit (e.g.,
"30"alone,"15 intervals").
day/calendar, week, month, and year, with optional business/working/work qualifiers for business-day arithmetic.
signing_date or claimed_deadline cannot be parsed, the guard returns verified=False and is_computable=False.
Features
| Feature | Description |
|---|---|
| Business vs Calendar | Automatically detects “business days” vs “days” |
| Holiday Support | 200+ countries via python-holidays |
| Leap Years | Handles Feb 29 correctly |
| Natural Language | Parses “2 weeks”, “3 months”, “1 year” |
| Fail-closed parsing | Rejects ambiguous legal language (“reasonable period”, “promptly”) instead of inventing a deadline |
Fail-closed behavior on ambiguous terms
DeadlineGuard only computes a deadline when the term contains both an explicit numeric quantity and a recognized time unit (day, business day, calendar day, week, month, year). When either is missing, the guard fails closed: it returns verified=False, is_computable=False, and a computed_deadline of None instead of guessing a default.
This protects against silent acceptance of subjective legal language such as "within a reasonable period", "promptly", "as soon as practicable", "without undue delay", or "forthwith". Resolving these terms requires human legal interpretation.
is_computable before relying on computed_deadline or difference_days. When is_computable is False, route the contract clause to a human reviewer.
Calculate business days between dates
2. LiabilityGuard
Status:DETERMINISTIC
Purpose: Verify liability cap and indemnity calculations for supported numeric inputs.
The problem
LLMs get percentage math wrong:- “200% of 15M” ❌ (Should be $10M)
- Float precision errors on large amounts
- Tiered liability miscalculations
Constructor parameters
Tolerance for floating-point comparison as a percentage. For example,
0.01 means 0.01% tolerance. Adjust for stricter or more lenient verification.The solution
verify_cap parameters
Total value of the contract.
Liability cap as a percentage (e.g.,
200 for 200%).The cap amount claimed by the LLM.
Response fields
| Field | Type | Description |
|---|---|---|
verified | bool | Whether the claimed cap matches the computed cap |
contract_value | Decimal | The contract value used |
cap_percentage | Decimal | The percentage used |
claimed_cap | Decimal | The cap claimed by the LLM |
computed_cap | Decimal | The correct cap computed by the guard |
difference | Decimal | Absolute difference between claimed and computed |
message | str | Human-readable verification message |
Additional methods
3. ClauseGuard
Status:PARTIAL / HEURISTIC
Purpose: Detect a limited set of contradictory clauses using text heuristics, with optional Z3-based satisfiability checks. A “consistent” result is not a proof of full contractual consistency.
The problem
LLMs miss logical contradictions:- “Seller may terminate with 30 days notice”
- “Neither party may terminate before 90 days”
The solution
The primarycheck_consistency() method uses text heuristics to detect conflicts. For formal logic verification, use verify_using_z3().
Detection types
| Conflict Type | Description |
|---|---|
| Termination | Notice period vs minimum term |
| Permission/Prohibition | ”May” vs “May not” |
| Exclusivity | Multiple exclusive rights |
Z3-based verification
When you need to define precise logical constraints,verify_using_z3() only accepts explicit Z3 BoolRef expressions — it does not parse free-form text. You must model the legal meaning yourself.
Fail-closed behavior
verify_using_z3() is fail-closed: it returns consistent=False for any input it cannot prove satisfiable. The following inputs are rejected as UNVERIFIABLE rather than silently passing:
| Input | Result | Message prefix |
|---|---|---|
Empty list [] | consistent=False | UNVERIFIABLE: verify_using_z3 requires explicit Z3 constraint expressions... |
Non-BoolRef values (e.g. strings, ints) | consistent=False | UNVERIFIABLE: verify_using_z3 only accepts explicit Z3 Boolean expressions... |
Z3 returns unknown | consistent=False | UNVERIFIABLE: Z3 returned unknown for the provided constraints. |
Satisfiable (sat) | consistent=True | VERIFIED: Provided Z3 constraints are satisfiable. |
Unsatisfiable (unsat) | consistent=False | CONTRADICTION: Provided Z3 constraints are unsatisfiable... |
4. CitationGuard
Status:PARTIAL / HEURISTIC
Purpose: Validate that legal citations match a supported format. CitationGuard does not prove that a cited authority exists or is controlling — it only checks structural shape against supported reporters.
The problem
The Mata v. Avianca scandal: Lawyers used ChatGPT, which cited 6 fake court cases. They were fined $5,000 and sanctioned.The solution
Supported citation patterns
| Pattern | Format | Example |
|---|---|---|
| US Supreme Court | volume U.S. page | 347 U.S. 483 |
| US Federal | volume F./F.2d/F.3d page | 500 F.3d 120 |
| UK Neutral | [year] court number | [2023] UKSC 10 |
| India AIR | AIR year court page | AIR 2020 SC 100 |
Batch verification
Statute citations
5. JurisdictionGuard
Status:PARTIAL / HEURISTIC
Purpose: Apply structured checks around governing law and forum selection clauses for modeled combinations. Results should not be treated as authoritative legal opinions on choice-of-law conflicts.
The problem
LLMs miss jurisdiction conflicts:- Governing law in one country, forum in another
- Missing CISG applicability warnings
- Cross-border legal system mismatches
The solution
Parameters
List of ISO country codes for contract parties (e.g.,
["US", "UK"]).The stated governing law — can be a country code or US state name/abbreviation (e.g.,
"Delaware", "DE", "UK").The stated forum or venue for dispute resolution.
Type of jurisdiction clause. Accepts
JurisdictionType.EXCLUSIVE, JurisdictionType.NON_EXCLUSIVE, or JurisdictionType.HYBRID.Features
| Feature | Description |
|---|---|
| Choice of Law | Validates governing law makes sense for parties |
| Forum Selection | Checks forum vs governing law alignment |
| CISG Detection | Warns about international sale of goods conventions |
| Convention Check | Verifies Hague, NY Convention applicability |
| Legal System Mismatch | Detects cross-border Common Law vs Civil Law conflicts |
Verify forum selection
Useverify_forum_selection to validate a forum independently, with optional contract value threshold checks for US federal court diversity jurisdiction:
Convention check
6. StatuteOfLimitationsGuard
Status:MIXED
Purpose: Compute claim limitation periods for supported jurisdictions and claim types using rule tables. The limitation-period lookup is PARSED; the date arithmetic over it (expiration, days remaining) is DETERMINISTIC. Coverage is limited to the modeled jurisdictions and claim types listed below.
The problem
LLMs don’t track jurisdiction-specific limitation periods:- California breach of contract: 4 years
- New York breach of contract: 6 years
- Different periods for negligence, fraud, etc.
The solution
Fail-closed behavior
StatuteOfLimitationsGuard is fail-closed: it never fabricates a limitation period for jurisdictions or claim types that are not in its rule tables.
- Exact match only. Jurisdiction lookup uses exact string equality (case-insensitive, trimmed). Partial matches such as
"CALIF"for"CALIFORNIA"or"NEW"for"NEW YORK"are rejected. - Unknown jurisdiction → unverifiable. If the jurisdiction is not in the supported list,
verify()returnsverified=Falsewithjurisdiction_matched=False, all date and period fields set toNone, and a message listing the supported jurisdictions. - Unknown claim type → unverifiable. If the jurisdiction is supported but the claim type is not modeled for it,
verify()returnsverified=Falsewithclaim_type_matched=False, and a message listing the supported claim types for that jurisdiction.
Parameters
Type of legal claim (e.g.,
"breach_of_contract", "negligence", "fraud"). Must exactly match one of the supported claim types for the given jurisdiction; unknown values produce an unverifiable result.State or country name (e.g.,
"California", "New York", "UK"). Matched case-insensitively against the supported jurisdictions list — partial or substring matches are not accepted.Date the incident occurred (ISO format).
Date the claim was or will be filed (ISO format).
Optional LLM claim to verify. When provided, the guard checks whether the LLM’s assertion (within/outside period) matches the computed result.
StatuteResult fields
True only when the jurisdiction and claim type are recognized and the filing falls within the limitation period (and matches claimed_within_period, if supplied).The claim type passed in (echoed back).
The jurisdiction passed in (echoed back).
Parsed incident date, or
None if date parsing failed.Parsed filing date, or
None if date parsing failed.Limitation period applied, or
None if the jurisdiction or claim type is unknown.Computed expiration date, or
None if no limitation period could be determined.Days between filing date and expiration (negative if expired), or
None if no limitation period could be determined.Human-readable result. Unverifiable results are prefixed with
⚠️ UNVERIFIABLE: and list the supported jurisdictions or claim types.False when the jurisdiction is not in the supported list.False when the claim type is not modeled for the given jurisdiction.Supported jurisdictions
12 jurisdictions are supported with periods for 10 claim types.| Jurisdiction | Breach of Contract | Negligence | Fraud |
|---|---|---|---|
| California | 4 years | 2 years | 3 years |
| New York | 6 years | 3 years | 6 years |
| Texas | 4 years | 2 years | 4 years |
| Delaware | 3 years | 2 years | 3 years |
| Florida | 5 years | 4 years | 4 years |
| Illinois | 5 years | 2 years | 5 years |
| UK/England | 6 years | 6 years | 6 years |
| Germany | 3 years | 3 years | 10 years |
| France | 5 years | 5 years | 5 years |
| Australia | 6 years | 6 years | 6 years |
| India | 3 years | 3 years | 3 years |
| Canada | 2 years | 2 years | 6 years |
Supported claim types
breach_of_contract, breach_of_warranty, negligence, professional_malpractice, fraud, personal_injury, property_damage, employment, product_liability, defamation
Fail-closed on unknown jurisdictions and claim types
StatuteOfLimitationsGuard only computes limitation periods for the jurisdictions and claim types it has explicit rules for. Anything outside that table fails closed instead of returning a fabricated period.
- Exact jurisdiction match only. Inputs are uppercased and trimmed before lookup. Substring matches like
"CALIF"no longer resolve to"CALIFORNIA", and a misspelled or unsupported jurisdiction never falls back to a generic default rule table. - Exact claim type match only. Claim types are lowercased with spaces converted to underscores before lookup. Unknown claim types no longer silently default to a 3-year period.
UNVERIFIABLEresult. When either lookup fails,verify()returns aStatuteResultwithverified=False, all date and period fields set toNone, and amessagethat begins with⚠️ UNVERIFIABLEand lists the supported values.- New flags.
StatuteResultexposesjurisdiction_matched: boolandclaim_type_matched: boolso callers can distinguish “claim is time-barred” from “we cannot determine the limit”.
Get limitation period
Look up the limitation period for a specific claim type and jurisdiction without performing a full verification. ReturnsNone when the jurisdiction or claim type is not supported:
Compare jurisdictions
compare_jurisdictions() returns Dict[str, Optional[float]]. Unsupported jurisdictions map to None so you can surface them in the UI rather than mixing them with real periods:
None rather than a default value.
7. IRACGuard
Status:PARTIAL / HEURISTIC
Purpose: Check that legal reasoning follows the IRAC framework (Issue, Rule, Application, Conclusion). IRACGuard verifies structure and surface-level consistency only — it is not a proof of correct legal reasoning.
The problem
LLMs produce legal advice that lacks structured reasoning:- Missing clear identification of the legal issue
- No citation of applicable rules or statutes
- Conclusions without proper application of law to facts
The solution
result["verified"] is always False for IRACGuard — structural validity is not proof of correct legal reasoning. Branch on structure_valid and status instead, and route unverifiable_reasoning results to a human reviewer.Detection types
| Check | Description |
|---|---|
| Structure | Verifies all 4 IRAC components are present |
| Logical Disconnect | Detects when Application doesn’t reference the Rule |
| Missing Steps | Identifies which IRAC components are missing |
Error response
8. FairnessGuard
Status:HEURISTIC / FAIL-CLOSED
Purpose: Apply a counterfactual consistency check that flags when output changes after protected attributes are swapped. This is a heuristic signal, not a fairness proof. Requires an external LLM client.
The problem
AI legal systems can exhibit bias based on protected attributes:- Different sentencing recommendations based on gender
- Inconsistent contract assessments based on party names
- Discriminatory loan approval reasoning
The solution
How it works
- Input validation — Rejects an empty swap, non-string values, and keys that collide when lowercased (fail-closed
ValueErrororUNVERIFIABLE_FAIRNESS). - Counterfactual generation — Swaps protected attributes (names, pronouns) in a single pass while preserving case.
- Re-evaluation — Runs the modified prompt through the LLM.
- Heuristic comparison — Compares outcomes by string equality. Consistency is reported as
UNVERIFIABLE_FAIRNESS(not proof); a difference is aHEURISTIC_BIAS_SIGNAL.
Response fields
| Field | Type | Description |
|---|---|---|
verified | bool | Always False. Fairness is never proven by this guard. |
status | str | UNVERIFIABLE_FAIRNESS or LLM_GENERATION_FAILED |
risk | str | HEURISTIC_BIAS_SIGNAL (outcomes differed) or LLM_GENERATION_FAILED. Present only when applicable. |
message | str | Explanation of the result |
variance | dict | Present only when outcomes differ — contains original and counterfactual decisions |
verification_trace | list | VerificationStep records; steps are HEURISTIC/UNSUPPORTED, never DETERMINISTIC |
Outcomes
| status / risk | Meaning |
|---|---|
UNVERIFIABLE_FAIRNESS (consistent) | Outcomes matched under one swap — not proof of fairness |
HEURISTIC_BIAS_SIGNAL (differing) | Outcome changed under swap — route to human review |
UNVERIFIABLE_FAIRNESS (empty swap) | No protected attributes provided — fail-closed |
LLM_GENERATION_FAILED | The LLM client returned None for the counterfactual prompt |
Migration from earlier versions: if your code branched on
result["verified"] == True or status == "FAIRNESS_VERIFIED", update it to treat the output as a signal. Consume status / risk and route HEURISTIC_BIAS_SIGNAL (and consistent-but-unverifiable) results to a human reviewer.9. ContradictionGuard
Status:MIXED
Purpose: Detect logical contradictions between modeled clauses using a Z3 constraint solver. The SAT/UNSAT result is DETERMINISTIC; clause categorization from text is PARSED. Coverage is limited to the supported clause categories below — a “consistent” result is not a proof of full contract consistency, and unmodeled clauses fail closed.
The problem
Contracts can contain mathematically impossible combinations:- “Liability capped at 50,000”
- “Term is exactly 12 months” + “Minimum duration of 24 months”
The solution
Clause structure
TheClause dataclass requires:
| Field | Type | Description |
|---|---|---|
id | str | Unique clause identifier |
text | str | Human-readable clause text |
category | str | DURATION, LIABILITY, or TERMINATION |
value | int | Normalized numeric value (days, dollars, etc.) |
Supported categories
| Category | Detects |
|---|---|
DURATION | Conflicting term lengths (exact vs min/max) |
LIABILITY | Cap vs penalty contradictions |
Z3 vs ClauseGuard
| Feature | ClauseGuard | ContradictionGuard |
|---|---|---|
| Input | Raw text strings | Structured Clause objects |
| Method | Text heuristics | Z3 SMT Solver |
| Detects | Permission conflicts | Mathematical impossibilities |
| Use Case | Quick checks | Formal verification |
10. ProvenanceGuard
Status:DETERMINISTIC
Purpose: Verify AI-generated content carries proper provenance metadata and disclosure markers. All checks are deterministic (SHA-256 hashing, regex pattern matching, datetime validation).
The problem
AI transparency regulations (California CAITA 2026, EU AI Act Article 50) require AI-generated legal content to carry proper attribution. Without verification:- Content may lack required AI-generation disclosures
- Provenance metadata can be incomplete or tampered with
- Unauthorized models may generate legal documents without audit trails
The solution
Verification checks
ProvenanceGuard runs up to six checks. The first three always run; the last three are configurable.| Check | Description | Always runs |
|---|---|---|
| Metadata completeness | content_hash, model_id, and generation_timestamp are present and non-empty | Yes |
| Hash integrity | SHA-256 of the content matches content_hash in provenance | Yes |
| Timestamp validity | ISO-8601 format, not in the future | Yes |
| Disclosure compliance | Content includes an AI-generation disclosure statement | If require_disclosure=True |
| Model allowlist | model_id is in the approved list | If allowed_models is set |
| Human review | human_reviewed is True in provenance | If require_human_review=True |
Constructor parameters
Require AI disclosure text in the content (e.g., “AI-generated”, “produced by AI”).
Require
human_reviewed=True in provenance metadata.Allowlist of model IDs.
None allows all models; an empty list denies all.Generating provenance records
You can also use ProvenanceGuard to generate provenance metadata:ProvenanceRecord fields
| Field | Type | Description |
|---|---|---|
content_hash | str | SHA-256 hash of the AI-generated content |
model_id | str | Identifier of the model that generated the content |
generation_timestamp | str | ISO-8601 timestamp of generation |
disclosure_text | str | Human-readable AI disclosure statement |
human_reviewed | bool | Whether a human has reviewed the content |
reviewer_id | str | None | Identifier of the human reviewer |
Risk classifications
When verification fails, therisk field indicates the type of failure:
| Risk | Trigger |
|---|---|
CONTENT_TAMPERED | Hash mismatch between content and content_hash |
INCOMPLETE_PROVENANCE | Required metadata fields missing or empty |
MISSING_DISCLOSURE | No AI-generation disclosure found in content |
UNAUTHORIZED_MODEL | model_id not in the allowed models list |
UNREVIEWED_CONTENT | human_reviewed is not True |
INVALID_TIMESTAMP | Timestamp is malformed or in the future |
ProvenanceGuard is fully deterministic — no LLM calls required. All checks use SHA-256 hashing, regex pattern matching, and datetime validation.
SACProcessor (RAG helper) 📄
Purpose: Prevent Document-Level Retrieval Mismatch (DRM) in legal RAG systems.The problem
Standard RAG chunking causes >95% retrieval mismatch in legal databases because:- Legal documents share nearly identical boilerplate
- Chunk-level embeddings lose document context
- NDAs, contracts, and agreements look alike at the chunk level
The solution
Configuration
| Parameter | Default | Description |
|---|---|---|
target_summary_length | 150 | Character limit for document fingerprint |
preview_chars | 5000 | Max chars sent to LLM for summarization |
Methods
| Method | Description |
|---|---|
generate_sac_chunks() | Augment all chunks with document fingerprint |
generate_fingerprint_only() | Get just the fingerprint for caching |
SACProcessor requires an LLM client. Generic (automated) summaries outperform expert-guided ones for retrieval.
All-in-one: LegalGuard
For convenience, use the unifiedLegalGuard class:
LegalGuard is a convenience wrapper. It does not change the verification boundaries of the underlying guards. DeadlineGuard, LiabilityGuard, and ProvenanceGuard are deterministic for supported inputs; the remaining guards are partial or heuristic. Only verify_fairness() requires an LLM client.Next steps
- Examples - Real-world scenarios
- Troubleshooting - Common issues