Reviewing a contract’s clauses: what is risky, how risky, and what to fix.
policy-risk.py · output · proof · check · try it in the playground
Terms of service are full of clauses: “we may remove your account”, “we may change these terms”, “we may share your data”. Some come with safeguards (notice, consent); some don’t.
Which clauses are risky, in what order should we look at them, and what would fix each one?
This is a small illustrative model: the scores are made-up parameters, not legal advice.
fact(permission('c1', 'remove_account'))
fact(permission('c2', 'change_terms'))
fact(notice_days('c2', 3))
fact(permission('c3', 'share_data'))
fact(prohibition('c4', 'export_data'))
fact(permission('c5', 'share_data'))
fact(safeguard('c5', 'consent'))
fact(permission('c6', 'change_terms'))
fact(notice_days('c6', 14))
fact(required_notice(14))
fact(importance('consent', 12))
# … and three more importance weights
c3 and c5 both allow sharing data, but only c5 asks for consent.
Each kind of risk has a rule. This one flags sharing without consent:
implies(
permission(Clause, 'share_data')
& ~safeguard(Clause, 'consent')
& importance('consent', Weight)
& is_(Raw, 85 + Weight),
finding(Clause, Raw, 'sharing_without_consent', 'require_consent'),
)
~ means “not”: the rule fires only if no consent safeguard is known.
Further rules cap scores at 100, label them high (80+), moderate (50–79) or low, and give each finding a rank: 1 plus the number of findings that score higher.
report(1, 'c1', 100, 'high', 'no_removal_safeguards', 'add_notice_and_inform')
report(2, 'c3', 97, 'high', 'sharing_without_consent', 'require_consent')
report(3, 'c2', 85, 'high', short_notice(3, 14), increase_notice(14))
report(4, 'c4', 70, 'moderate', 'export_prohibited', 'permit_export')
(Sorted by rank here; peye prints them in the order it found them.)
Each line: rank, clause, score, severity, the reason, and a suggested fix. c5 and c6 do not appear: c5 has consent, and c6 gives the full 14 days’ notice.
Take clause c1, which lets the provider remove your account:
Steps 2 and 5 rely on “nothing more to find”, which the next card is about.
absent obligations — “c1 has no notice”, “c1 has no inform
safeguard”, “c3 has no consent safeguard”. peye searched and found
nothing; the checker records this rather than proving it.collected obligations — the lists of higher scores used for each
rank, e.g. for 85 the list [100, 97]. That each list is complete is
recorded, not proved.Verdict: checked_with_obligations.
python -m peye examples/policy-risk.py # the report
python -m peye --check-proof examples/proof/policy-risk.py examples/policy-risk.py # the check
Or open it in the playground.
Add fact(safeguard('c3', 'consent')) and run again: the c3 finding disappears, and
c2 and c4 move up to ranks 2 and 3.
A risk list is only useful if you can see why each item is on it. Here every finding carries its reason and fix, and the report spells out exactly which “nothing was found” assumptions the ranking depends on.