For testing, we have context managers that let you do "with temporary_gate('gate_name', value)", so there's not much boilerplate in overriding/testing your code within a gate constraint.
if A allow, else continue to next rule
if B deny, else continue to next rule
if C allow, else continue to next rule
deny all
For example, ufw rules have that form.
I see a few advantages compared to Boolean operators:
1. Easier to diff and version control.
2. Easier to build UI for, for example, to show which rule fails for a particular user.
3. Simpler to implement, no recursion in the DSL.
4. Arguably simpler to use.
5. Immediately obvious how short-circuiting works.
Why did you choose to go with Boolean operators rather than rules?
I do agree with your point 1,2,3, rule-based is better in some cases, but it's not as expressive, sometimes you have to express the logic in a non-straightforward way to satisfy your need, when the logic get complicated. We're trying to make the system flexible from the backend, and improve the usability at the UX level. For example, the constants inside the language will go to a separate section in the UI and will have various components of tuning them.
If the DSL code is organized well, it's going to look as clean as the rule based one.
I believe Java/C# both have this kind of tool to hook into the compiler and generate bytecode, either official or 3rd-party. For Java it might be asm, don't know a lot about C#, but since Microsoft has open-sourced their compiler, I think that's possible.
A lot of dynamic languages do provide native interpreter interaction, like Python AST, I'd say it makes things a lot easier, but totally doable in other "not-so-dynamic" language.
Might be hard with total compiled languages like C++ though, otherwise we have to distribute gcc/clang to the production machine :-p