/ 01Why a rule, not a model
A model gives you a probability and a vibe. A rule gives you a pattern, a citation, a severity, and an explanation — and it does so deterministically. For regulated work the difference matters: when a partner asks why did this fire, "the model thought so" is not an answer that survives.
Redline rules are written as JSON and live in legal/pack/{jurisdiction}/{N}_{topic}.json. They are loaded into the Legal pack pass (see the pipeline) and evaluated against the document with the active jurisdiction and sector filter applied.
/ 02Rule schema
The canonical shape:
{
"id": "eng.05_ucta_liability.cap_below_threshold",
"pattern": "(liability|liabilities)\\s+(of|under)\\s+(this|the)\\s+agreement\\s+(is|are)\\s+(limited|capped)",
"suppress_if": "(except\\s+for|carve-out|gross\\s+negligence|wilful\\s+misconduct)",
"scope": "clause",
"absent": false,
"category": "liability_caps",
"severity": "major",
"confidence_base": 0.85,
"summary": "Liability cap may be unenforceable under UCTA s.3",
"explanation": "Section 3 of the Unfair Contract Terms Act 1977 invalidates...",
"suggestion": "Raise the cap to at least 100% of fees paid in the 12 months...",
"citation": "UCTA 1977 s.3; St Albans City & DC v ICL [1996]"
}
Fields
idrequiredStable identifier. Format{jurisdiction}.{file}.{name}. Used in finding objects and audit logs — never change without a migration.patternrequiredPythonreregex (RE2-compatible subset). Case-insensitive by default. Match groups are surfaced in the finding.suppress_ifoptionalSecond regex. If a match forpatternco-occurs with a match forsuppress_ifin the samescope, the finding is suppressed.scopeclause · paragraph · documentWhere the pattern must match. Most rules are clause-scoped; cross-cutting rules are document-scoped.absentboolWhentrue, the rule fires if the pattern is missing from the scope. This is how you require a clause to exist.severitycritical · major · minor · suggestionDrives UI colour and PDF report ordering.confidence_base0.0 – 1.0Starting confidence. The engine adjusts it based on the match quality.
/ 03Patterns: presence vs absence
Two shapes account for almost every rule.
Presence — flag when the pattern matches
The default. Used for "this clause is wrong because it says X." Set absent: false (or omit).
Absence — flag when the pattern is missing
Used for "this contract is missing a required clause." Set absent: true. The pattern becomes a positive signature for the thing that should be present.
{
"id": "eng.20_force_majeure.required_clause",
"pattern": "force\\s+majeure|act\\s+of\\s+god|circumstances\\s+beyond",
"scope": "document",
"absent": true,
"severity": "minor",
"summary": "No force majeure clause detected"
}
/ 04Suppression
The single biggest source of false positives is matching a phrase that is later carved out. Suppression handles this.
When suppress_if is set, the rule's match window is widened to the surrounding clause (or paragraph, per scope), and the rule only fires if the suppression pattern does not appear in that window.
Suppression patterns are inclusive — match anywhere in the scope, including the same sentence as the main pattern. Use negative lookaheads sparingly; they're costly and the engine caps lookahead complexity at 64 characters.
/ 05Severity & confidence
| Severity | When to use | UI |
|---|---|---|
critical | Statutory non-compliance, unenforceable provision, hard breach | Red |
major | Significant risk, likely unenforceable in part, missing required clause | Amber |
minor | Drafting weakness, advisory non-compliance | Yellow |
suggestion | Best practice, style, optional improvement | Grey |
Confidence is multiplicative: confidence_base × clause-classification confidence × span-context score. A rule whose confidence_base is 0.85 firing in a clause with 0.92 classification confidence and 0.95 context score gives a finding at 0.74. Findings below the silo's confidence_floor (default 0.55) are dropped before the response.
/ 06Worked example: late payment interest
The Late Payment of Commercial Debts (Interest) Act 1998 sets a statutory interest rate on overdue B2B invoices. A contract that disclaims it is usually unenforceable. Here is the rule:
{
"id": "eng.15_financial.late_payment_disclaim",
"pattern": "(no|without|waive(s|d)?)\\s+(claim|right|entitlement)\\s+to\\s+(statutory\\s+)?interest",
"suppress_if": "(consumer|individual\\s+capacity|non-?commercial)",
"scope": "clause",
"severity": "critical",
"confidence_base": 0.88,
"summary": "Disapplication of statutory interest may be unenforceable",
"citation": "Late Payment of Commercial Debts (Interest) Act 1998 ss.8–9"
}
/ 07Validation & CI
Three checks run before a rule is accepted.
Schema validation
Against
rule.v3.json. Missing required fields, malformed regex, unknown severity — all caught at parse time.Corpus calibration
scripts/corpus/contradiction_validator.pyruns the rule against the 81,428-document corpus. False-positive rate above silo threshold (default 3%) blocks merge.Synthetic adversarial
scripts/corpus/synthetic_generator.pygenerates 200 negation- and carve-out-shaped variations. The suppression pattern must catch at least 85%.
The CLI:
redline rules validate eng/15_financial.json redline rules calibrate eng/15_financial.json --corpus prod --report redline rules diff --against main eng/15_financial.json
