🏛️ Verification guards for legal contracts - Date calculations, clause consistency, liability verification
Project description
QWED-Legal
Deterministic Verification Guards for Computational Legal Claims
Block unproven legal claims before they become liabilities.
QWED-Legal verifies only what can be deterministically proven.
Dates, amounts, structured constraints
QWED-Legal verifies ONLY what can be deterministically proven (dates, amounts, constraints).
Interpretive legal reasoning is NOT automatically trusted.
The Problem
LLMs can produce legal-sounding output that is wrong, incomplete, or unprovable.
| Failure mode | Example | Risk |
|---|---|---|
| Fabricated authority | AI cites a nonexistent or malformed legal source | Sanctions, bad filings, bad advice |
| Deadline mistakes | "30 business days" is miscomputed | Missed obligations, defaults |
| Clause inconsistency | Two provisions cannot both be true | Disputes, unenforceable terms |
| False certainty | Model states a legal conclusion without proof | Liability, audit failure |
QWED-Legal is designed to sit between untrusted model output and downstream action.
What QWED-Legal Is
QWED-Legal is a verification layer for deterministic, computational legal claims.
It is built to:
- verify only deterministic, provable components such as dates, calculations, and structured constraints
- reject or mark unverified claims that cannot be safely proven
- operate as a fail-closed middleware layer in legal AI workflows
- integrate into existing LLM, review, or contract-analysis pipelines
Verification Boundaries
QWED-Legal operates under strict limits:
- only deterministic claims can be verified
- ambiguous or interpretive outputs are rejected or marked unverified
- legal reasoning is not assumed correct without proof
- if something cannot be proven, it must not pass
This repository should be understood as a deterministic rejection layer, not a general-purpose legal reasoning engine.
❌ What QWED-Legal Is Not
QWED-Legal is not:
- a legal reasoning engine
- a source of legal truth
- a replacement for lawyers
- a contract drafting tool
- a legal research assistant
- a guarantee that every legal output can be verified
- a system that can prove subjective, ambiguous, or interpretive legal conclusions
QWED-Legal is a rejection layer. It blocks what cannot be proven.
Positioning
QWED-Legal is not another end-to-end legal AI assistant.
It does not compete by generating more legal text. It is intended to check narrow classes of legal claims that can be computed or constrained deterministically.
| Aspect | Generative legal tools | QWED-Legal |
|---|---|---|
| Primary role | Draft, summarize, classify, or answer | Verify narrow deterministic claims |
| Approach | Probabilistic generation | Deterministic checks where possible |
| Scope | Broad legal text tasks | Limited (computational checks only) |
| Output style | Plausible legal answer | Verified / blocked / unverified result |
| Certainty | Model confidence | 100% certainty only for supported deterministic checks |
| Failure mode | Can hallucinate or overstate | Should fail closed when proof is unavailable |
| Deployment | Often SaaS or model-centric | SDK / middleware / local verification layer |
Recommended usage
LLM or legal workflow -> QWED-Legal -> accept only what survives verification
Quick Start
Installation
| Language | Package | Command |
|---|---|---|
| Python | qwed-legal |
pip install qwed-legal |
| TypeScript / JS | @qwed-ai/legal |
npm install @qwed-ai/legal |
pip install qwed-legal
npm install @qwed-ai/legal
Verify a structured deadline claim
Use deterministic checks only with structured, unambiguous inputs.
from qwed_legal import DeadlineGuard
guard = DeadlineGuard()
result = guard.verify(
signing_date="2026-01-15",
term="30 business days",
claimed_deadline="2026-02-14"
)
print(result.verified)
print(result.computed_deadline)
print(result.message)
Verify a liability cap calculation
from qwed_legal import LiabilityGuard
guard = LiabilityGuard()
result = guard.verify_cap(
contract_value=5_000_000,
cap_percentage=200,
claimed_cap=15_000_000
)
print(result.verified)
print(result.computed_cap)
print(result.message)
Check a structured contradiction scenario
from qwed_legal import ContradictionGuard, Clause
guard = ContradictionGuard()
result = guard.verify_consistency([
Clause(id="1", text="Liability capped at $10k", category="LIABILITY", value=10000),
Clause(id="2", text="Minimum penalty is $50k", category="LIABILITY", value=50000),
])
print(result["verified"])
print(result["message"])
Guard Coverage
⚠️ Not all guards provide full formal verification.
Some operate on partial rules or structured validation and should not be treated as complete legal proof.
| Guard | Status | What it checks |
|---|---|---|
DeadlineGuard |
DETERMINISTIC |
Date arithmetic, business-day calculations, holiday-aware computations for supported inputs |
LiabilityGuard |
DETERMINISTIC |
Cap calculations, tiered amount computations for supported numeric inputs |
ClauseGuard |
PARTIAL / HEURISTIC |
Limited clause consistency and contradiction checks (explicit Z3 path is deterministic) |
CitationGuard |
PARTIAL / HEURISTIC |
Citation shape / format validation, not authoritative existence proof |
JurisdictionGuard |
PARTIAL / HEURISTIC |
Structured checks around governing law / forum combinations |
StatuteOfLimitationsGuard |
MIXED |
Deterministic date arithmetic over a parsed limitation-period lookup (lookup itself is PARSED, not authority proof) |
IRACGuard |
PARTIAL / HEURISTIC |
IRAC structure and consistency checks, not proof of legal reasoning |
ContradictionGuard |
MIXED |
Deterministic Z3 SAT/UNSAT over a limited set of modeled clause categories; unmodeled inputs fail closed |
FairnessGuard |
HEURISTIC / FAIL-CLOSED |
Counterfactual consistency signal only; never returns verified=True — fairness cannot be proven by text substitution (issue #18) |
ProvenanceGuard |
DETERMINISTIC |
AI-content provenance/disclosure checks: hash integrity, metadata completeness, timestamp validity, disclosure/model/human-review policy |
Verification trace (auditability)
Every guard returns a verification_trace — an ordered list of VerificationStep
records that make each decision auditable. A trace is not a narrative
explanation; each step carries an evidence_type that classifies how its
output was derived:
evidence_type |
Meaning | is_proven() |
|---|---|---|
DETERMINISTIC |
Proven by math/logic (Z3, date arithmetic, exact compare) | True |
PARSED |
Read/matched from structure or lookup table — 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 |
Only DETERMINISTIC steps constitute actual proof. PARSED, INFERRED,
HEURISTIC, and UNSUPPORTED steps must not be treated as verification.
from qwed_legal import StatuteOfLimitationsGuard, trace_to_dict
result = StatuteOfLimitationsGuard().verify(
claim_type="breach_of_contract",
jurisdiction="California",
incident_date="2020-01-01",
filing_date="2023-01-01",
)
for step in result.verification_trace:
print(step.step, step.evidence_type, "->", step.output)
# JSON-safe export for audit logs / external consumers
serialized = trace_to_dict(result.verification_trace)
# each entry includes an explicit "is_proven" flag
Example: citation format check
from qwed_legal import CitationGuard
guard = CitationGuard()
result = guard.verify("Brown v. Board of Education, 347 U.S. 483 (1954)")
print(result.format_valid) # True if the string matches a known reporter pattern
print(result.status) # 'unverifiable_authority' even when format is valid
print(result.verified) # always False — authority cannot be proven
print(result.parsed_components)
Important: a valid format result does not prove that a cited authority exists or is controlling. It only means the citation matched a supported structural pattern. result.verified is therefore always False, and result.status is unverifiable_authority whenever the format matches — CitationGuard has no case-law database.
QWED Legal Triangle
QWED-Legal is safest when used across three separate trust questions:
-
Output Accuracy (the "what")
- Typical guards:
DeadlineGuard,LiabilityGuard - Role: verify computational claims such as dates, percentages, and amounts
- Typical guards:
-
Reasoning Structure (the "how")
- Typical guards:
IRACGuard,ClauseGuard,ContradictionGuard - Role: apply structure and consistency checks to reasoning, but full logical proof is not guaranteed
- Typical guards:
-
Source / Retrieval Integrity (the "where from")
- Typical component:
SACProcessor - Role: improve traceability and chunking quality for legal retrieval workflows
- Typical component:
QWED-Legal does not collapse these into one claim of "full legal verification."
Components
Jurisdiction checks
from qwed_legal import JurisdictionGuard
guard = JurisdictionGuard()
result = guard.verify_choice_of_law(
parties_countries=["US", "UK"],
governing_law="Delaware",
forum="London",
)
print(result.conflicts)
print(result.warnings)
Statute of limitations checks
from qwed_legal import StatuteOfLimitationsGuard
guard = StatuteOfLimitationsGuard()
result = guard.verify(
claim_type="breach_of_contract",
jurisdiction="California",
incident_date="2020-01-15",
filing_date="2026-06-01",
)
print(result.verified)
print(result.message)
These results should be treated as valid only for supported, unambiguous, and modeled inputs.
TypeScript / JavaScript
npm install @qwed-ai/legal
Available verifiers
| Verifier | Description |
|---|---|
DeadlineVerifier |
Verify structured date calculations |
LiabilityVerifier |
Verify liability cap arithmetic |
ClauseVerifier |
Run limited clause consistency checks |
CitationVerifier |
Check supported citation formats |
JurisdictionVerifier |
Check structured governing-law / forum combinations |
StatuteVerifier |
Check supported limitation periods |
LegalGuard |
Convenience wrapper |
TypeScript example
import {
DeadlineVerifier,
JurisdictionVerifier,
StatuteVerifier,
LegalGuard,
} from "@qwed-ai/legal";
const deadline = new DeadlineVerifier();
const deadlineResult = await deadline.verify("2026-01-15", "30 days", "2026-02-14");
console.log(deadlineResult.verified);
const jurisdiction = new JurisdictionVerifier();
const jResult = await jurisdiction.verifyChoiceOfLaw(
["US", "UK"],
"Delaware",
"London",
);
console.log(jResult.conflicts);
const guard = new LegalGuard();
const statute = await guard.statute.verify(...);
Supported Jurisdictions
Statute of limitations
| 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 |
| 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 |
DeadlineGuard holiday support
| Region | Countries / States |
|---|---|
| United States | All 50 states + DC |
| European Union | DE, FR, IT, ES, NL, BE, AT, PL |
| Commonwealth | UK, AU (all states), CA |
| Asia | IN (all states), SG, HK |
Support coverage does not imply that every legal deadline term is verifiable. Inputs still need to be structured and deterministic.
Examples of Claims QWED-Legal Can Reject
These are examples of supported checks catching unsupported claims. They are not proof that every legal hallucination is detectable.
| Input | Claimed result | Example outcome |
|---|---|---|
| "Net 30 business days from Dec 20" | Wrong computed date | Blocked by DeadlineGuard |
| "Liability cap: 2x fees" | Wrong cap arithmetic | Blocked by LiabilityGuard |
| Structured liability conflict | "Clauses are consistent" | Blocked by ContradictionGuard |
| Unsupported citation reporter | "Valid citation" | Blocked by CitationGuard format checks |
All-in-One Guard
from qwed_legal import LegalGuard
guard = LegalGuard()
guard.verify_deadline(...)
guard.verify_liability_cap(...)
guard.check_clause_consistency(...)
This wrapper is for convenience. It does not change the proof boundaries of the underlying guards.
Security & Privacy
Your data does not need to leave your machine for deterministic checks.
| Concern | QWED-Legal approach |
|---|---|
| Data transmission | No cloud calls for local deterministic guards. FairnessGuard is the main exception and depends on an external client you provide. |
| Storage | No required persistent storage for core verification flows |
| Dependencies | Local math / logic libraries plus optional external LLM integration for fairness workflows |
| Auditability | Deterministic checks are reproducible for the same supported inputs |
Verification does not imply correctness of legal interpretation. It only implies correctness of the specific properties that were checked.
Architecture
flowchart LR
subgraph "Untrusted Input"
A["LLM / workflow output"]
end
subgraph "QWED-Legal"
B["DeadlineGuard"]
C["LiabilityGuard"]
D["ClauseGuard"]
E["CitationGuard"]
F["JurisdictionGuard"]
G["StatuteOfLimitationsGuard"]
H["ContradictionGuard"]
I["IRACGuard"]
J["FairnessGuard"]
end
subgraph "Engines"
K["Arithmetic / date logic"]
L["Constraint solver"]
M["Rule tables"]
N["Optional external LLM"]
end
A --> B --> K
A --> C --> K
A --> D --> L
A --> E --> M
A --> F --> M
A --> G --> M
A --> H --> L
A --> I --> M
A --> J --> N
LLM output is treated as untrusted input.
QWED does not assume correctness. It requires proof for the properties it is able to verify.
Integration Examples
With OpenAI
from openai import OpenAI
from qwed_legal import DeadlineGuard
client = OpenAI()
guard = DeadlineGuard()
def verified_deadline_response(prompt: str) -> dict:
response = client.chat.completions.create(
model="gpt-4",
messages=[{"role": "user", "content": prompt}],
)
llm_answer = response.choices[0].message.content
verification = guard.verify(
signing_date="2026-01-15",
term="30 business days",
claimed_deadline=llm_answer,
)
return {
"llm_response": llm_answer,
"verified": verification.verified,
"verification_message": verification.message,
}
With LangChain
from langchain.tools import StructuredTool
from pydantic import BaseModel, Field
from qwed_legal import LegalGuard
guard = LegalGuard()
class DeadlineInput(BaseModel):
signing_date: str = Field(..., description="Contract signing date")
term: str = Field(..., description="Structured deadline term")
claimed_deadline: str = Field(..., description="Claimed deadline to verify")
class LiabilityInput(BaseModel):
contract_value: float = Field(..., description="Base contract value")
cap_percentage: float = Field(..., description="Liability cap percentage")
claimed_cap: float = Field(..., description="Claimed cap amount")
qwed_deadline_tool = StructuredTool.from_function(
name="verify_deadline",
description="Verify a structured deadline calculation",
func=guard.verify_deadline,
args_schema=DeadlineInput,
)
qwed_liability_tool = StructuredTool.from_function(
name="verify_liability",
description="Verify a liability cap calculation",
func=guard.verify_liability_cap,
args_schema=LiabilityInput,
)
tools = [qwed_deadline_tool, qwed_liability_tool]
FAQ
Is QWED-Legal free?
Yes. QWED-Legal is open source under the Apache 2.0 license.
Does it call external APIs?
Core deterministic guards do not require external APIs. FairnessGuard is the main exception and depends on an external LLM client you supply.
How accurate is it?
100% accurate for supported deterministic checks such as math and date computations.
For interpretive legal reasoning, QWED-Legal may only partially validate structure or consistency and should fail closed when proof is not possible.
Can QWED verify legal reasoning?
No. Legal reasoning often involves ambiguity, interpretation, and context that cannot be formally proven. QWED-Legal can only verify specific, structured aspects of such reasoning.
Can it replace my legal AI tool?
No. It is a verification layer, not a drafting or reasoning engine.
What happens when verification fails?
The claim should be blocked, rejected, or surfaced as unverified. The goal is to prevent unproven legal claims from quietly passing downstream.
What happens when verification is not possible?
The correct outcome is not "best guess." The correct outcome is to fail closed, reject the claim, or mark it unverified.
Roadmap
Focus: expanding deterministic verification coverage, not heuristic reasoning.
Current coverage
- Deadline calculations
- Liability computations
- Structured contradiction checks
- Citation format checks
- Jurisdiction / statute support for modeled inputs
- TypeScript / npm SDK
In progress
- IP clause verification
- Indemnity verification
- More supported jurisdictions
Planned
- Force majeure completeness checks
- Non-compete rule coverage
- More deterministic contract logic coverage
- IDE and workflow integrations
Related QWED Packages
| Package | Purpose |
|---|---|
| qwed-verification | Core verification engine |
| qwed-finance | Banking and financial verification |
| qwed-tax | Tax verification |
| qwed-ucp | Commerce / transaction verification |
| qwed-mcp | Desktop / workflow integration |
License
Apache 2.0 - See LICENSE
"In law, unproven output should not pass."
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file qwed_legal-0.4.0.tar.gz.
File metadata
- Download URL: qwed_legal-0.4.0.tar.gz
- Upload date:
- Size: 559.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f77e7a9cd36164b790de2b6e43680dea2998e918f86bf330bd608e13798d61b1
|
|
| MD5 |
75125d498a79d6f30bb52a91a84f303d
|
|
| BLAKE2b-256 |
64be55673f817a47bc3ce7171c3c35ebe2b9d39d7735e856a85f2c7fb41b0f7c
|
Provenance
The following attestation bundles were made for qwed_legal-0.4.0.tar.gz:
Publisher:
publish.yml on QWED-AI/qwed-legal
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
qwed_legal-0.4.0.tar.gz -
Subject digest:
f77e7a9cd36164b790de2b6e43680dea2998e918f86bf330bd608e13798d61b1 - Sigstore transparency entry: 1675353216
- Sigstore integration time:
-
Permalink:
QWED-AI/qwed-legal@1322e51350e0b1966134feb9609bb8aae64cfa4c -
Branch / Tag:
refs/tags/v0.4.0 - Owner: https://github.com/QWED-AI
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@1322e51350e0b1966134feb9609bb8aae64cfa4c -
Trigger Event:
release
-
Statement type:
File details
Details for the file qwed_legal-0.4.0-py3-none-any.whl.
File metadata
- Download URL: qwed_legal-0.4.0-py3-none-any.whl
- Upload date:
- Size: 59.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
523115f699cc7db1e392023e21d751d595c39ee05a118b722efea612ec699b9d
|
|
| MD5 |
694b9d7372b4953eeecd1bc13f590a4f
|
|
| BLAKE2b-256 |
ef3a9299a0bc7b4fae7c113fd5e75d374a48afc15997f120e3eb45a547055753
|
Provenance
The following attestation bundles were made for qwed_legal-0.4.0-py3-none-any.whl:
Publisher:
publish.yml on QWED-AI/qwed-legal
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
qwed_legal-0.4.0-py3-none-any.whl -
Subject digest:
523115f699cc7db1e392023e21d751d595c39ee05a118b722efea612ec699b9d - Sigstore transparency entry: 1675353258
- Sigstore integration time:
-
Permalink:
QWED-AI/qwed-legal@1322e51350e0b1966134feb9609bb8aae64cfa4c -
Branch / Tag:
refs/tags/v0.4.0 - Owner: https://github.com/QWED-AI
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@1322e51350e0b1966134feb9609bb8aae64cfa4c -
Trigger Event:
release
-
Statement type: