- Rust 100%
| .forgejo/workflows | ||
| .github/workflows | ||
| fuzz | ||
| src | ||
| tests | ||
| .gitignore | ||
| Cargo.toml | ||
| CHANGELOG.md | ||
| deny.toml | ||
| LICENSE-APACHE | ||
| LICENSE-MIT | ||
| README.md | ||
| rust-toolchain.toml | ||
legal-rules
Deterministic German tenancy-law checks for landlord-to-tenant letters.
This crate is a pure rules engine. It has no HTTP client, database, AWS SDK, clock, filesystem access, or runtime state. Given the same typed input, it returns the same decision.
It covers the existing repr behavior for:
- § 558 BGB rent-increase gates: residential use, agreed rent, twelve-month freeze, 15% / 36-month Kappungsgrenze, consent/effective-date timing, and German-language confirmation.
- § 573 / § 573c BGB termination gates: stated legal ground, proof-capable delivery, notice-period steps, month-end effectiveness, arrears evidence, prior warning, and German-language confirmation.
- Structural validation of a finished draft: addressee, sender, statutory citation, language, rent amount, and effective date.
This is not legal advice and does not inspect facts. Callers remain responsible for supplying accurate rent history, dates, warnings, and arrears evidence.
Install
The package is publishable but not yet on crates.io. Until the first release, use a path dependency from this checkout:
[dependencies]
legal-rules = { path = "../legal-rules" }
After a tagged release, use a pinned registry dependency:
[dependencies]
legal-rules = "0.1"
The library name is legal_rules.
The standalone repository is
florian/legal-rules. The
legal-rules/ directory in repr is the source of truth until the first
registry release; the standalone repository is a subtree export of this
directory, not a second implementation.
Quickstart
use legal_rules::{
check_letter, ActionType, LetterContext, LetterLanguage, SendChannel,
};
use time::{Date, Month};
let context = LetterContext {
is_commercial: false,
agreed_rent: 1_000.0,
proposed_rent: 1_090.0,
notice_date: Date::from_calendar_date(2026, Month::January, 10)?,
proposed_effective_date: Date::from_calendar_date(2026, Month::April, 1)?,
historic_rents: vec![(
Date::from_calendar_date(2024, Month::January, 1)?,
1_000.0,
)],
tenancy_start: Some(Date::from_calendar_date(2020, Month::January, 1)?),
legal_ground: None,
arrears: None,
prior_written_warning: false,
send_channel: SendChannel::SimpleLetter,
letter_language: Some(LetterLanguage::German),
};
let decision = check_letter(ActionType::DraftRentIncrease, context);
assert!(decision.allowed);
assert!(decision.requires_human_approval);
# Ok::<(), time::error::ComponentRange>(())
allowed == false is a hard block. requires_human_approval remains true for
allowed rent-increase and termination letters; reminders and non-letter actions
remain automatic.
API contract
| API | Purpose |
|---|---|
check_letter(action, context) |
Decide whether an action may draft or send a letter. |
validate_letter_draft(draft) |
Validate required fields, citations, language, and dates. |
detect_german(text) |
Conservative, fail-closed German-language heuristic. |
SendChannel::is_einwurfsicher() |
Whether a channel provides proof of receipt. |
All input and result types are re-exported at crate root. Error results are a
Vec<String> of human-readable problems; there is no hidden state or I/O
error. The crate deliberately avoids a narrower builder API: callers construct
the documented plain structs directly, including in compatibility tests.
Feature matrix
| Feature | Default | Effect |
|---|---|---|
| none | n/a | The crate has no optional features or cloud/runtime dependencies. |
The only runtime dependency is time, used
for date types and civil date comparisons.
Scope boundaries
This package carries over the deterministic behavior of the original
backend/aws/crates/legal-rules package. It intentionally does not add:
- municipal rent-control ordinances or the separate § 558m cap, which need reviewed jurisdiction-specific inputs and legal review;
- fact finding, PDF rendering, delivery, persistence, authentication, or AI;
- a claim that a letter is legally sufficient in a particular case.
Unknown future inputs must extend the typed public API with a reviewed contract rather than being guessed from free text.
MSRV
Rust 1.89. The crate uses edition 2021 and is tested on stable Rust.
Semver policy
The crate follows semantic versioning for the public typed API. Breaking changes
to public types, functions, enum variants, or documented decision semantics
require a major release after 1.0, and a minor release while on 0.x.
Additive fields on public structs are breaking because callers construct them,
so they follow the same rule.
Testing
cargo fmt --all -- --check
cargo clippy --all-targets --all-features -- -D warnings
cargo test --all-features
cargo test --doc
cargo doc --no-deps
cargo deny check
cargo llvm-cov --all-features --summary-only --fail-under-lines 95
cargo check --manifest-path fuzz/Cargo.toml
The test suite includes old-caller compatibility tests, date and cap boundaries,
empty and oversized inputs, property tests, and fuzz targets for both
validate_letter_draft and detect_german. Run the fuzzers with
cargo +nightly fuzz run validate_letter_draft and
cargo +nightly fuzz run detect_german.
Release
Publishing requires a human with a crates.io token. Do not add publish credentials to the repository.
- Export this directory to the standalone
florian/legal-rulesrepository (the directory is already a self-contained crate root). - Run every gate in Testing in a clean checkout.
- Set
CARGO_REGISTRY_TOKENas a repository secret. It must belong to thefloffelcrates.io publisher and must never be committed. - Run
cargo publish --dry-run, thencargo publishfrom a tagged release commit. - Switch consumers from a path dependency to a pinned registry version after the release is visible.
The local package is ready for that workflow; creating the external repository, tagging, and publishing are intentionally left to an authorized maintainer.
License
Licensed under either of Apache License, Version 2.0 or MIT license at your option.