// Reading the shelf: Redline Fieldbook Atrium v3.0 · 2026-03
Documentation / Redline · Review /Legal pack/Authoring a rule How-to · 18 min

Authoring a legal-pack rule.

Legal-pack rules are JSON documents — versioned, signed, and validated against a corpus of 81,428 commercial agreements before they reach production. This is how you write one.

TypeHow-to
Reading18 min
Revisedv3.0 · 2026-03
Applies toRedline Review · tier B+

/ 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

  • idrequired
    Stable identifier. Format {jurisdiction}.{file}.{name}. Used in finding objects and audit logs — never change without a migration.
  • patternrequired
    Python re regex (RE2-compatible subset). Case-insensitive by default. Match groups are surfaced in the finding.
  • suppress_ifoptional
    Second regex. If a match for pattern co-occurs with a match for suppress_if in the same scope, the finding is suppressed.
  • scopeclause · paragraph · document
    Where the pattern must match. Most rules are clause-scoped; cross-cutting rules are document-scoped.
  • absentbool
    When true, the rule fires if the pattern is missing from the scope. This is how you require a clause to exist.
  • severitycritical · major · minor · suggestion
    Drives UI colour and PDF report ordering.
  • confidence_base0.0 – 1.0
    Starting 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.

Note

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

SeverityWhen to useUI
criticalStatutory non-compliance, unenforceable provision, hard breachRed
majorSignificant risk, likely unenforceable in part, missing required clauseAmber
minorDrafting weakness, advisory non-complianceYellow
suggestionBest practice, style, optional improvementGrey

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.

  1. Schema validation

    Against rule.v3.json. Missing required fields, malformed regex, unknown severity — all caught at parse time.

  2. Corpus calibration

    scripts/corpus/contradiction_validator.py runs the rule against the 81,428-document corpus. False-positive rate above silo threshold (default 3%) blocks merge.

  3. Synthetic adversarial

    scripts/corpus/synthetic_generator.py generates 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

Read carefully. Then begin.

Request access Back to documentation