Baseline policy
refund.create
allow
≤ $100
Permission change · before release
Detect when a permission change grants more authority.
Baseline policy
refund.create
allow
≤ $100
Candidate policy
refund.create
allow
≤ $100
Scenario · refund.create
$150
Reading the candidate ceiling.
File values: 10_000 → 50_000 minor units (100 minor units = $1 USD · $100 → $500). The crossed scenario is 15_000 ($150).
A text diff sees two numbers. PermitDiff sees that a refund was denied before and allowed after the policy change.
The check
One corpus watches observed behavior. A separate rule pass watches authority the corpus may miss.
A higher ceiling, a removed guard, and a reordered rule set are different policy changes. PermitDiff runs the same scenarios against baseline and candidate, then examines static authority when scenario coverage is insufficient.
Concrete transition
refund.create · $100 → $500
10,000 → 50,000 minor units · 100 minor units = $1 USD
refund.create · $50
Baselineallow
Candidateallow
Unchanged
refund.create · $150
Baselinedeny
Candidateallow
Expansion
search.query · $0
Baselinedeny
Candidatedeny
Unchanged
The $150 scenario (15,000 minor units at 100 minor units = $1 USD) crosses the former ceiling. Static analysis independently records rule_scope_broadened with reason authority_expansion.
quickstart.pyPASS
Identical, or restriction only, after both channels.
REVIEW
ceiling exampleAn observed expansion, or a static sign of one.
BLOCK
reorder exampleA changed rule cannot be shown to be non-expanding.
Measurement contract
EVALUATION METHOD
A permission change needs a review when the same scenario is denied before and allowed after.
Public reference quickstart: refund.create ceiling 10_000 → 50_000 minor units ($100 → $500 at 100 minor units = $1 USD), scenarios at $50 (5_000), $150 (15_000), and search.query at 0.
What the evidence actually supports.
The public quickstart shows refund.create at $150 (15,000 minor units) move from deny to allow when the ceiling rises from $100 to $500 (10,000 → 50,000 minor units; 100 minor units = $1 USD).
This is one runnable reference example, not an external benchmark.
No external corpus result is claimed.
Local reference tests are not independent validation.
Engineering decisions
Constraints that keep an authority diff from becoming a decorative policy comparison.
The ceiling change is one number. The $150 refund is a new allow.
A scenario corpus can miss a rule. A static check can be incomplete. Neither one gets to hide the other.
If a changed rule cannot be shown to be non-expanding, the public verdict is block.
The amounts on the case study are the amounts in examples/quickstart.py.
Forensic reorder
The corpus records a restriction. The reordered rules still cannot be proven non-expanding.
Baseline
different order
Candidate
Unknown semantics fail closed. Not a runtime authorizer · Not a security proof · Not production IAM · No external validation claimed
Provenance boundary
One product name. Two evidence surfaces.
Public reference
Private alpha
A public REVIEW corresponds to a private gate failure until an exact waiver exists. This site does not link to the private repository.
Reproduction
pin bc9fa27git clone https://github.com/TensorScholar/permitdiff-reference cd permitdiff-reference git checkout bc9fa27bde27caa21083604a91315e497e678199 python3 examples/quickstart.py
decision: review
observed: {'same': 2, 'expanded': 1, 'restricted': 0, 'unknown': 0}
findings: ['rule_scope_broadened:refund-allow']
reasons: ['authority_expansion']
PermitDiff reference comparison
baseline: baseline v0 digest=80c0a334ae89...
candidate: candidate v1 digest=6e93f3d9c299...
corpus: 3 scenarios digest=cb0a3af02493...
Summary:
privilege_expansions: 1
restrictions: 0
unknown_transitions: 0
new_allows: 1
approval_bypasses: 0
static_authority_expansions: 1
static_authority_unknowns: 0
Transitions:
[unchanged/note] corpus-000: refund.create amount=5000 unchanged (allow (rule refund-allow)); no review signal.
[expanded/high] corpus-001: refund.create amount=15000 deny (default) -> allow (rule refund-allow).
[unchanged/note] corpus-002: search.query amount=0 unchanged (deny (default)); no review signal.
Static findings:
[potential_expansion] rule_scope_broadened: Rule 'refund-allow': allow scope broadened (match relation: broader).
Unknown semantics fail closed. See examples/README.md for the full suite.