eyeprolog 1.3.20 → 1.3.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
# ODRL policy reasoning
|
|
2
|
+
|
|
3
|
+
## One reasoner, many kinds of policy questions
|
|
4
|
+
|
|
5
|
+
A policy system rarely needs to answer only one question.
|
|
6
|
+
|
|
7
|
+
It may need to decide whether an action is allowed, detect contradictory rules,
|
|
8
|
+
compare a specific rule with a more general one, or admit that the available
|
|
9
|
+
rules do not determine a unique answer.
|
|
10
|
+
|
|
11
|
+
The `odrl-policy-reasoning.pl` example shows how those questions can live in
|
|
12
|
+
one executable logical model.
|
|
13
|
+
|
|
14
|
+
[Run the example in the EyeProlog playground](https://eyereasoner.github.io/eyeprolog/playground#example=odrl-policy-reasoning)
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
# The problem is bigger than permit or deny
|
|
19
|
+
|
|
20
|
+
A realistic policy engine may be asked:
|
|
21
|
+
|
|
22
|
+
- **Enforcement:** may Alice print this report?
|
|
23
|
+
- **Conditions:** does the permission apply only for research use?
|
|
24
|
+
- **Duties:** is attribution required before distribution is allowed?
|
|
25
|
+
- **Conflicts:** what if one rule permits an action and another prohibits it?
|
|
26
|
+
- **Subsumption:** does a broad policy already cover a narrower policy?
|
|
27
|
+
- **Incomplete information:** can the rules justify a definite answer at all?
|
|
28
|
+
|
|
29
|
+
These are different questions, but they are questions about the same policy
|
|
30
|
+
model.
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
# One small policy world
|
|
35
|
+
|
|
36
|
+
The example contains:
|
|
37
|
+
|
|
38
|
+
- parties such as `alice`, `bob`, `analysts`, and `anyone`;
|
|
39
|
+
- assets such as reports and datasets;
|
|
40
|
+
- actions such as `use`, `display`, `print`, `read`, and `aggregate`;
|
|
41
|
+
- permissions and prohibitions;
|
|
42
|
+
- constraints and duties;
|
|
43
|
+
- requests asking whether a concrete action may be performed.
|
|
44
|
+
|
|
45
|
+
For example, a policy can permit analysts to `use` reports while separately
|
|
46
|
+
prohibiting Alice from `print`ing one particular report.
|
|
47
|
+
|
|
48
|
+
That is enough to create a genuine policy conflict.
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
# Actions are related, not isolated
|
|
53
|
+
|
|
54
|
+
Policy actions can stand in several useful relationships.
|
|
55
|
+
|
|
56
|
+
```prolog
|
|
57
|
+
broader(use, present).
|
|
58
|
+
broader(present, display).
|
|
59
|
+
broader(use, print).
|
|
60
|
+
|
|
61
|
+
requires(aggregate, read).
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
So a rule about `use` can also be relevant to a request to `display` or
|
|
65
|
+
`print`, while `aggregate` may depend on `read` even though neither action is a
|
|
66
|
+
specialization of the other.
|
|
67
|
+
|
|
68
|
+
The example can therefore answer six kinds of action-matching question:
|
|
69
|
+
|
|
70
|
+
`exact`, `broader`, `narrower`, `required`, `requiring`, and `no_match`.
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
# Rules can be active, inactive, or irrelevant
|
|
75
|
+
|
|
76
|
+
A rule does not automatically apply just because its action looks similar to
|
|
77
|
+
the requested action.
|
|
78
|
+
|
|
79
|
+
The reasoner also checks the party, asset, constraints, and duties.
|
|
80
|
+
|
|
81
|
+
For example:
|
|
82
|
+
|
|
83
|
+
```prolog
|
|
84
|
+
permission(context_policy, research_use, analysts, use, reports).
|
|
85
|
+
constraint(research_use, purpose, research).
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
A research request can activate this permission. A commercial request reaches
|
|
89
|
+
the same rule but fails its constraint, so the rule is **inactive**. A request
|
|
90
|
+
for an unrelated action is simply **not applicable**.
|
|
91
|
+
|
|
92
|
+
This distinction matters: "the rule applies but its condition failed" is not
|
|
93
|
+
the same as "this rule has nothing to say about the request".
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
# Enforcement combines the applicable rules
|
|
98
|
+
|
|
99
|
+
Once individual rules have been evaluated, the policy can answer the practical
|
|
100
|
+
question: what should the system do?
|
|
101
|
+
|
|
102
|
+
The example demonstrates:
|
|
103
|
+
|
|
104
|
+
- an active permission becoming `permit`;
|
|
105
|
+
- an active prohibition becoming `deny`;
|
|
106
|
+
- a failed duty or constraint becoming `deny` in the closed evaluator;
|
|
107
|
+
- an unmatched request being permitted by an `open` evaluator;
|
|
108
|
+
- the same unmatched request being denied by `closed` or `default` behaviour.
|
|
109
|
+
|
|
110
|
+
So enforcement is built from the same rule facts used by the other reasoning
|
|
111
|
+
questions rather than being a separate policy representation.
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
# Conflicting rules need a policy decision
|
|
116
|
+
|
|
117
|
+
Suppose a request matches both a permission and a prohibition.
|
|
118
|
+
|
|
119
|
+
The example runs the same kind of conflict under all three ODRL conflict
|
|
120
|
+
strategies:
|
|
121
|
+
|
|
122
|
+
- `perm` — the permission wins;
|
|
123
|
+
- `prohibit` — the prohibition wins;
|
|
124
|
+
- `invalid` — the conflict invalidates the result.
|
|
125
|
+
|
|
126
|
+
This keeps **detecting a conflict** separate from **deciding what a policy does
|
|
127
|
+
with that conflict**.
|
|
128
|
+
|
|
129
|
+
That separation is useful because different policies may deliberately resolve
|
|
130
|
+
the same logical conflict in different ways.
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
# Conflicts can be indirect
|
|
135
|
+
|
|
136
|
+
Two rules do not need to mention exactly the same action to interfere with one
|
|
137
|
+
another.
|
|
138
|
+
|
|
139
|
+
The example detects three forms:
|
|
140
|
+
|
|
141
|
+
1. **Exact conflict** — permission and prohibition concern the same action.
|
|
142
|
+
2. **Subsumption conflict** — a broad permission such as `use` overlaps a
|
|
143
|
+
narrower prohibition such as `print`.
|
|
144
|
+
3. **Dependency conflict** — a permitted action such as `aggregate` requires
|
|
145
|
+
another action such as `read`, while `read` is prohibited.
|
|
146
|
+
|
|
147
|
+
This is why an action model is important: looking only for identical action
|
|
148
|
+
names would miss meaningful policy conflicts.
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
# Subsumption asks "does this already cover that?"
|
|
153
|
+
|
|
154
|
+
Subsumption turns policy comparison into another executable query.
|
|
155
|
+
|
|
156
|
+
At the action level:
|
|
157
|
+
|
|
158
|
+
```prolog
|
|
159
|
+
?- action_subsumes(use, display).
|
|
160
|
+
true.
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
At the rule level, the comparison also takes parties, assets, constraints, and
|
|
164
|
+
duties into account.
|
|
165
|
+
|
|
166
|
+
At the policy level, the reasoner asks whether every rule in a more specific
|
|
167
|
+
policy is covered by some rule in the more general policy.
|
|
168
|
+
|
|
169
|
+
This supports questions such as:
|
|
170
|
+
|
|
171
|
+
> Does policy A already express everything required by policy B?
|
|
172
|
+
|
|
173
|
+
The example contains both positive and negative cases for action, rule, and
|
|
174
|
+
whole-policy subsumption.
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
# Sometimes "yes or no" is the wrong choice
|
|
179
|
+
|
|
180
|
+
Rule systems can contain recursion through negation.
|
|
181
|
+
|
|
182
|
+
In simplified form:
|
|
183
|
+
|
|
184
|
+
```prolog
|
|
185
|
+
permission(X) :- tnot(prohibition(X)).
|
|
186
|
+
prohibition(X) :- tnot(permission(X)).
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
For a request caught in this negative cycle, arbitrarily choosing one side
|
|
190
|
+
would invent information that the rules do not justify.
|
|
191
|
+
|
|
192
|
+
EyeProlog uses **Well-Founded Semantics (WFS)**, which has three truth states:
|
|
193
|
+
|
|
194
|
+
- **true** — the claim can be established;
|
|
195
|
+
- **false** — its negation can be established;
|
|
196
|
+
- **undefined** — neither side is justified because the reasoning is cyclic.
|
|
197
|
+
|
|
198
|
+
---
|
|
199
|
+
|
|
200
|
+
# Undefined is useful information
|
|
201
|
+
|
|
202
|
+
The example deliberately asks three WFS questions:
|
|
203
|
+
|
|
204
|
+
```text
|
|
205
|
+
clear_permission -> true
|
|
206
|
+
absent_permission -> false
|
|
207
|
+
negative_cycle -> undefined
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
`undefined` is not an error and it is not a random third answer.
|
|
211
|
+
|
|
212
|
+
It tells the application that the policy knowledge itself does not determine a
|
|
213
|
+
stable true/false conclusion.
|
|
214
|
+
|
|
215
|
+
An enforcement layer can then choose how to handle that uncertainty — for
|
|
216
|
+
example by denying conservatively, asking for more information, or escalating
|
|
217
|
+
for review — without corrupting the logical result.
|
|
218
|
+
|
|
219
|
+
---
|
|
220
|
+
|
|
221
|
+
# Why put these questions in one reasoner?
|
|
222
|
+
|
|
223
|
+
A mixed *reasoning* approach can still have a single declarative home.
|
|
224
|
+
|
|
225
|
+
The example uses different logical capabilities where they fit:
|
|
226
|
+
|
|
227
|
+
- hierarchy and transitive reasoning for action relationships;
|
|
228
|
+
- ordinary rules for applicability and enforcement;
|
|
229
|
+
- structural reasoning for conflict detection and subsumption;
|
|
230
|
+
- negation and WFS for recursive defaults and incomplete conclusions.
|
|
231
|
+
|
|
232
|
+
They are not forced into one algorithm. They coexist behind one language, one
|
|
233
|
+
knowledge model, and one query mechanism.
|
|
234
|
+
|
|
235
|
+
That is the sense in which EyeProlog can be **"a reasoner to run them all."**
|
|
236
|
+
|
|
237
|
+
---
|
|
238
|
+
|
|
239
|
+
# What this example is — and is not
|
|
240
|
+
|
|
241
|
+
`odrl-policy-reasoning.pl` demonstrates the **reasoning layer** in plain
|
|
242
|
+
EyeProlog.
|
|
243
|
+
|
|
244
|
+
It is not intended to be a complete ODRL JSON-LD parser or a replacement for
|
|
245
|
+
the full ODRL information model. Other repository examples demonstrate RDF and
|
|
246
|
+
ODRL data handling.
|
|
247
|
+
|
|
248
|
+
Its purpose is narrower and practical: show that enforcement, conflict
|
|
249
|
+
analysis, subsumption, dependencies, constraints, duties, and three-valued WFS
|
|
250
|
+
reasoning can be queried coherently in one executable model.
|
|
251
|
+
|
|
252
|
+
---
|
|
253
|
+
|
|
254
|
+
# Try it yourself
|
|
255
|
+
|
|
256
|
+
Run the example in the browser:
|
|
257
|
+
|
|
258
|
+
**https://eyereasoner.github.io/eyeprolog/playground#example=odrl-policy-reasoning**
|
|
259
|
+
|
|
260
|
+
Then change one fact at a time:
|
|
261
|
+
|
|
262
|
+
- fulfil or remove a duty;
|
|
263
|
+
- change `research` to `commercial`;
|
|
264
|
+
- add or remove an action hierarchy edge;
|
|
265
|
+
- introduce a new prohibition;
|
|
266
|
+
- change the policy conflict strategy;
|
|
267
|
+
- create or break a negative cycle.
|
|
268
|
+
|
|
269
|
+
Then observe which enforcement, conflict, subsumption, and WFS answers change
|
|
270
|
+
together.
|
|
@@ -0,0 +1,411 @@
|
|
|
1
|
+
% ODRL-style policy reasoning in plain EyeProlog.
|
|
2
|
+
%
|
|
3
|
+
% This executable example models the reasoning layer rather than JSON-LD
|
|
4
|
+
% parsing. It covers ODRL 2.2 permissions, prohibitions, constraints, duties,
|
|
5
|
+
% the perm/prohibit/invalid conflict strategies, action dependencies, and
|
|
6
|
+
% subsumption questions. The final section shows how an ODRL profile with
|
|
7
|
+
% recursive defaults can use tnot/1: WFS keeps a negative cycle undefined
|
|
8
|
+
% instead of choosing an arbitrary permission or prohibition.
|
|
9
|
+
%
|
|
10
|
+
% References: https://www.w3.org/TR/odrl-model/#conflict
|
|
11
|
+
% https://w3c.github.io/odrl/formal-semantics/
|
|
12
|
+
|
|
13
|
+
%% goal: actionQuestion(X0, X1, X2, X3)
|
|
14
|
+
%% goal: ruleQuestion(X0, X1, X2, X3)
|
|
15
|
+
%% goal: enforcementQuestion(X0, X1, X2, X3, X4, X5)
|
|
16
|
+
%% goal: conflictQuestion(X0, X1, X2, X3, X4)
|
|
17
|
+
%% goal: subsumptionQuestion(X0, X1, X2, X3)
|
|
18
|
+
%% goal: wfsQuestion(X0, X1)
|
|
19
|
+
|
|
20
|
+
% --- ODRL action vocabulary -----------------------------------------------
|
|
21
|
+
|
|
22
|
+
% A small excerpt of the ODRL action hierarchy. broader(A, B) means that A
|
|
23
|
+
% is the more general action. For example, a rule about use can affect print.
|
|
24
|
+
action(use).
|
|
25
|
+
action(present).
|
|
26
|
+
action(display).
|
|
27
|
+
action(play).
|
|
28
|
+
action(print).
|
|
29
|
+
action(distribute).
|
|
30
|
+
action(reproduce).
|
|
31
|
+
action(aggregate).
|
|
32
|
+
action(extract).
|
|
33
|
+
action(read).
|
|
34
|
+
action(delete).
|
|
35
|
+
|
|
36
|
+
broader(use, present).
|
|
37
|
+
broader(present, display).
|
|
38
|
+
broader(present, play).
|
|
39
|
+
broader(use, print).
|
|
40
|
+
broader(use, distribute).
|
|
41
|
+
broader(use, reproduce).
|
|
42
|
+
|
|
43
|
+
% Implicit action dependencies are kept separate from explicit subsumption.
|
|
44
|
+
requires(aggregate, read).
|
|
45
|
+
requires(extract, read).
|
|
46
|
+
|
|
47
|
+
action_subsumes(Action, Action) :- action(Action).
|
|
48
|
+
action_subsumes(Broader, Narrower) :- broader(Broader, Narrower).
|
|
49
|
+
action_subsumes(Broader, Narrower) :-
|
|
50
|
+
broader(Broader, Middle),
|
|
51
|
+
action_subsumes(Middle, Narrower).
|
|
52
|
+
|
|
53
|
+
action_requires(Action, Required) :- requires(Action, Required).
|
|
54
|
+
action_requires(Action, Required) :-
|
|
55
|
+
requires(Action, Middle),
|
|
56
|
+
action_requires(Middle, Required).
|
|
57
|
+
|
|
58
|
+
% The six useful answers to "how does this rule action relate to the requested
|
|
59
|
+
% action?": exact, broader, narrower, required, requiring, or no_match.
|
|
60
|
+
positive_action_match(Action, Action, exact) :- action(Action).
|
|
61
|
+
positive_action_match(RuleAction, RequestAction, broader) :-
|
|
62
|
+
RuleAction \= RequestAction,
|
|
63
|
+
action_subsumes(RuleAction, RequestAction).
|
|
64
|
+
positive_action_match(RuleAction, RequestAction, narrower) :-
|
|
65
|
+
RuleAction \= RequestAction,
|
|
66
|
+
action_subsumes(RequestAction, RuleAction).
|
|
67
|
+
positive_action_match(RuleAction, RequestAction, required) :-
|
|
68
|
+
action_requires(RequestAction, RuleAction).
|
|
69
|
+
positive_action_match(RuleAction, RequestAction, requiring) :-
|
|
70
|
+
action_requires(RuleAction, RequestAction).
|
|
71
|
+
|
|
72
|
+
action_match(RuleAction, RequestAction, Kind) :-
|
|
73
|
+
positive_action_match(RuleAction, RequestAction, Kind).
|
|
74
|
+
action_match(RuleAction, RequestAction, no_match) :-
|
|
75
|
+
action(RuleAction),
|
|
76
|
+
action(RequestAction),
|
|
77
|
+
\+ positive_action_match(RuleAction, RequestAction, _).
|
|
78
|
+
|
|
79
|
+
% --- Parties and assets ----------------------------------------------------
|
|
80
|
+
|
|
81
|
+
party(alice).
|
|
82
|
+
party(bob).
|
|
83
|
+
party(analysts).
|
|
84
|
+
party(anyone).
|
|
85
|
+
member_of(alice, analysts).
|
|
86
|
+
member_of(bob, analysts).
|
|
87
|
+
|
|
88
|
+
party_subsumes(Party, Party) :- party(Party).
|
|
89
|
+
party_subsumes(Group, Person) :- member_of(Person, Group).
|
|
90
|
+
party_subsumes(anyone, Party) :- party(Party), Party \= anyone.
|
|
91
|
+
|
|
92
|
+
asset(report_q1).
|
|
93
|
+
asset(report_q2).
|
|
94
|
+
asset(reports).
|
|
95
|
+
asset(dataset_a).
|
|
96
|
+
asset(dataset_b).
|
|
97
|
+
asset(dataset_c).
|
|
98
|
+
|
|
99
|
+
asset_member(report_q1, reports).
|
|
100
|
+
asset_member(report_q2, reports).
|
|
101
|
+
|
|
102
|
+
asset_subsumes(Asset, Asset) :- asset(Asset).
|
|
103
|
+
asset_subsumes(Collection, Item) :- asset_member(Item, Collection).
|
|
104
|
+
|
|
105
|
+
% --- Policies, rules, requests, and evidence ------------------------------
|
|
106
|
+
|
|
107
|
+
% policy(Policy, ConflictStrategy).
|
|
108
|
+
policy(open_printing, perm).
|
|
109
|
+
policy(strict_printing, prohibit).
|
|
110
|
+
policy(invalid_printing, invalid).
|
|
111
|
+
policy(duty_policy, invalid).
|
|
112
|
+
policy(context_policy, invalid).
|
|
113
|
+
policy(analysis_policy, invalid).
|
|
114
|
+
policy(general_policy, invalid).
|
|
115
|
+
policy(specific_policy, invalid).
|
|
116
|
+
|
|
117
|
+
% permission/prohibition(Policy, Rule, Assignee, Action, Target).
|
|
118
|
+
permission(open_printing, open_use, analysts, use, reports).
|
|
119
|
+
prohibition(open_printing, open_no_print, alice, print, report_q1).
|
|
120
|
+
|
|
121
|
+
permission(strict_printing, strict_use, analysts, use, reports).
|
|
122
|
+
prohibition(strict_printing, strict_no_print, alice, print, report_q1).
|
|
123
|
+
|
|
124
|
+
permission(invalid_printing, invalid_use, analysts, use, reports).
|
|
125
|
+
prohibition(invalid_printing, invalid_no_print, alice, print, report_q1).
|
|
126
|
+
|
|
127
|
+
permission(duty_policy, share_report, alice, distribute, report_q1).
|
|
128
|
+
duty(share_report, attribute_source).
|
|
129
|
+
|
|
130
|
+
permission(context_policy, research_use, analysts, use, reports).
|
|
131
|
+
constraint(research_use, purpose, research).
|
|
132
|
+
|
|
133
|
+
% Three independent static conflicts: exact action, explicit action
|
|
134
|
+
% subsumption, and an implicit requires dependency.
|
|
135
|
+
permission(analysis_policy, exact_permit, alice, display, dataset_a).
|
|
136
|
+
prohibition(analysis_policy, exact_prohibit, alice, display, dataset_a).
|
|
137
|
+
permission(analysis_policy, broad_permit, alice, use, dataset_b).
|
|
138
|
+
prohibition(analysis_policy, narrow_prohibit, alice, print, dataset_b).
|
|
139
|
+
permission(analysis_policy, aggregate_permit, alice, aggregate, dataset_c).
|
|
140
|
+
prohibition(analysis_policy, read_prohibit, alice, read, dataset_c).
|
|
141
|
+
|
|
142
|
+
% A broad policy and a narrower policy for rule/policy subsumption questions.
|
|
143
|
+
permission(general_policy, general_use, analysts, use, reports).
|
|
144
|
+
permission(specific_policy, specific_display, alice, display, report_q1).
|
|
145
|
+
constraint(specific_display, purpose, research).
|
|
146
|
+
|
|
147
|
+
% request(Request, Assignee, Action, Target).
|
|
148
|
+
request(q_print, alice, print, report_q1).
|
|
149
|
+
request(q_share_done, alice, distribute, report_q1).
|
|
150
|
+
request(q_share_missing, alice, distribute, report_q1).
|
|
151
|
+
request(q_research, alice, display, report_q1).
|
|
152
|
+
request(q_commercial, alice, display, report_q1).
|
|
153
|
+
request(q_unrelated, alice, delete, report_q1).
|
|
154
|
+
|
|
155
|
+
evidence(q_research, purpose, research).
|
|
156
|
+
evidence(q_commercial, purpose, commercial).
|
|
157
|
+
duty_done(q_share_done, attribute_source).
|
|
158
|
+
|
|
159
|
+
rule(Rule, Policy, permission, Party, Action, Target) :-
|
|
160
|
+
permission(Policy, Rule, Party, Action, Target).
|
|
161
|
+
rule(Rule, Policy, prohibition, Party, Action, Target) :-
|
|
162
|
+
prohibition(Policy, Rule, Party, Action, Target).
|
|
163
|
+
|
|
164
|
+
% --- Rule evaluation -------------------------------------------------------
|
|
165
|
+
|
|
166
|
+
rule_scope_matches(Rule, Request) :-
|
|
167
|
+
rule(Rule, _, _, RuleParty, RuleAction, RuleTarget),
|
|
168
|
+
request(Request, Party, Action, Target),
|
|
169
|
+
party_subsumes(RuleParty, Party),
|
|
170
|
+
asset_subsumes(RuleTarget, Target),
|
|
171
|
+
positive_action_match(RuleAction, Action, _).
|
|
172
|
+
|
|
173
|
+
constraints_hold(Rule, Request) :-
|
|
174
|
+
\+ (constraint(Rule, Key, Value), \+ evidence(Request, Key, Value)).
|
|
175
|
+
|
|
176
|
+
duties_hold(Rule, Request) :-
|
|
177
|
+
\+ (duty(Rule, Duty), \+ duty_done(Request, Duty)).
|
|
178
|
+
|
|
179
|
+
permission_result(Rule, Request, permission) :-
|
|
180
|
+
permission(_, Rule, _, _, _),
|
|
181
|
+
rule_scope_matches(Rule, Request),
|
|
182
|
+
constraints_hold(Rule, Request),
|
|
183
|
+
duties_hold(Rule, Request).
|
|
184
|
+
permission_result(Rule, Request, inactive(constraint(Key))) :-
|
|
185
|
+
permission(_, Rule, _, _, _),
|
|
186
|
+
rule_scope_matches(Rule, Request),
|
|
187
|
+
constraint(Rule, Key, Value),
|
|
188
|
+
\+ evidence(Request, Key, Value).
|
|
189
|
+
permission_result(Rule, Request, inactive(duty(Duty))) :-
|
|
190
|
+
permission(_, Rule, _, _, _),
|
|
191
|
+
rule_scope_matches(Rule, Request),
|
|
192
|
+
constraints_hold(Rule, Request),
|
|
193
|
+
duty(Rule, Duty),
|
|
194
|
+
\+ duty_done(Request, Duty).
|
|
195
|
+
permission_result(Rule, Request, not_applicable) :-
|
|
196
|
+
permission(_, Rule, _, _, _),
|
|
197
|
+
\+ rule_scope_matches(Rule, Request).
|
|
198
|
+
|
|
199
|
+
prohibition_result(Rule, Request, prohibition) :-
|
|
200
|
+
prohibition(_, Rule, _, _, _),
|
|
201
|
+
rule_scope_matches(Rule, Request),
|
|
202
|
+
constraints_hold(Rule, Request).
|
|
203
|
+
prohibition_result(Rule, Request, inactive(constraint(Key))) :-
|
|
204
|
+
prohibition(_, Rule, _, _, _),
|
|
205
|
+
rule_scope_matches(Rule, Request),
|
|
206
|
+
constraint(Rule, Key, Value),
|
|
207
|
+
\+ evidence(Request, Key, Value).
|
|
208
|
+
prohibition_result(Rule, Request, not_applicable) :-
|
|
209
|
+
prohibition(_, Rule, _, _, _),
|
|
210
|
+
\+ rule_scope_matches(Rule, Request).
|
|
211
|
+
|
|
212
|
+
rule_result(Rule, Request, Result) :- permission_result(Rule, Request, Result).
|
|
213
|
+
rule_result(Rule, Request, Result) :- prohibition_result(Rule, Request, Result).
|
|
214
|
+
|
|
215
|
+
% --- Enforcement -----------------------------------------------------------
|
|
216
|
+
|
|
217
|
+
policy_permission(Policy, Request) :-
|
|
218
|
+
permission(Policy, Rule, _, _, _),
|
|
219
|
+
permission_result(Rule, Request, permission).
|
|
220
|
+
policy_prohibition(Policy, Request) :-
|
|
221
|
+
prohibition(Policy, Rule, _, _, _),
|
|
222
|
+
prohibition_result(Rule, Request, prohibition).
|
|
223
|
+
policy_inactive(Policy, Request) :-
|
|
224
|
+
rule(Rule, Policy, _, _, _, _),
|
|
225
|
+
rule_result(Rule, Request, inactive(_)).
|
|
226
|
+
policy_applicable(Policy, Request) :-
|
|
227
|
+
rule(Rule, Policy, _, _, _, _),
|
|
228
|
+
rule_scope_matches(Rule, Request).
|
|
229
|
+
|
|
230
|
+
% Conflicting permission/prohibition answers use the ODRL conflict strategy.
|
|
231
|
+
policy_result(Policy, Request, permission) :-
|
|
232
|
+
policy_permission(Policy, Request),
|
|
233
|
+
policy_prohibition(Policy, Request),
|
|
234
|
+
policy(Policy, perm).
|
|
235
|
+
policy_result(Policy, Request, prohibition) :-
|
|
236
|
+
policy_permission(Policy, Request),
|
|
237
|
+
policy_prohibition(Policy, Request),
|
|
238
|
+
policy(Policy, prohibit).
|
|
239
|
+
policy_result(Policy, Request, invalid) :-
|
|
240
|
+
policy_permission(Policy, Request),
|
|
241
|
+
policy_prohibition(Policy, Request),
|
|
242
|
+
policy(Policy, invalid).
|
|
243
|
+
|
|
244
|
+
% Non-conflicting active rules retain their deontic result.
|
|
245
|
+
policy_result(Policy, Request, permission) :-
|
|
246
|
+
policy_permission(Policy, Request),
|
|
247
|
+
\+ policy_prohibition(Policy, Request).
|
|
248
|
+
policy_result(Policy, Request, prohibition) :-
|
|
249
|
+
policy_prohibition(Policy, Request),
|
|
250
|
+
\+ policy_permission(Policy, Request).
|
|
251
|
+
policy_result(Policy, Request, inactive) :-
|
|
252
|
+
\+ policy_permission(Policy, Request),
|
|
253
|
+
\+ policy_prohibition(Policy, Request),
|
|
254
|
+
policy_inactive(Policy, Request).
|
|
255
|
+
policy_result(Policy, Request, not_applicable) :-
|
|
256
|
+
policy(Policy, _),
|
|
257
|
+
\+ policy_applicable(Policy, Request).
|
|
258
|
+
|
|
259
|
+
% Access-control enforcement adds the evaluator behaviour for the case where
|
|
260
|
+
% no rule applies. "default" is the ODRL evaluator's closed behaviour.
|
|
261
|
+
access_decision(Policy, Request, _Behaviour, permit) :-
|
|
262
|
+
policy_result(Policy, Request, permission).
|
|
263
|
+
access_decision(Policy, Request, _Behaviour, deny) :-
|
|
264
|
+
policy_result(Policy, Request, prohibition).
|
|
265
|
+
access_decision(Policy, Request, _Behaviour, invalid) :-
|
|
266
|
+
policy_result(Policy, Request, invalid).
|
|
267
|
+
access_decision(Policy, Request, _Behaviour, deny) :-
|
|
268
|
+
policy_result(Policy, Request, inactive).
|
|
269
|
+
access_decision(Policy, Request, open, permit) :-
|
|
270
|
+
policy_result(Policy, Request, not_applicable).
|
|
271
|
+
access_decision(Policy, Request, closed, deny) :-
|
|
272
|
+
policy_result(Policy, Request, not_applicable).
|
|
273
|
+
access_decision(Policy, Request, default, deny) :-
|
|
274
|
+
policy_result(Policy, Request, not_applicable).
|
|
275
|
+
|
|
276
|
+
% --- Static conflict detection --------------------------------------------
|
|
277
|
+
|
|
278
|
+
opposite(permission, prohibition).
|
|
279
|
+
opposite(prohibition, permission).
|
|
280
|
+
|
|
281
|
+
parties_overlap(A, B) :- party_subsumes(A, B).
|
|
282
|
+
parties_overlap(A, B) :- party_subsumes(B, A).
|
|
283
|
+
assets_overlap(A, B) :- asset_subsumes(A, B).
|
|
284
|
+
assets_overlap(A, B) :- asset_subsumes(B, A).
|
|
285
|
+
constraints_compatible(RuleA, RuleB) :-
|
|
286
|
+
\+ (constraint(RuleA, Key, A), constraint(RuleB, Key, B), A \= B).
|
|
287
|
+
|
|
288
|
+
conflicting_action(Action, Action, exact) :- action(Action).
|
|
289
|
+
conflicting_action(ActionA, ActionB, subsumption) :-
|
|
290
|
+
ActionA \= ActionB,
|
|
291
|
+
(action_subsumes(ActionA, ActionB); action_subsumes(ActionB, ActionA)).
|
|
292
|
+
conflicting_action(ActionA, ActionB, dependency) :-
|
|
293
|
+
\+ action_subsumes(ActionA, ActionB),
|
|
294
|
+
\+ action_subsumes(ActionB, ActionA),
|
|
295
|
+
(action_requires(ActionA, ActionB); action_requires(ActionB, ActionA)).
|
|
296
|
+
|
|
297
|
+
rule_conflict(RuleA, RuleB, Kind) :-
|
|
298
|
+
rule(RuleA, _, EffectA, PartyA, ActionA, TargetA),
|
|
299
|
+
rule(RuleB, _, EffectB, PartyB, ActionB, TargetB),
|
|
300
|
+
opposite(EffectA, EffectB),
|
|
301
|
+
parties_overlap(PartyA, PartyB),
|
|
302
|
+
assets_overlap(TargetA, TargetB),
|
|
303
|
+
constraints_compatible(RuleA, RuleB),
|
|
304
|
+
conflicting_action(ActionA, ActionB, Kind).
|
|
305
|
+
|
|
306
|
+
% --- Rule and policy subsumption ------------------------------------------
|
|
307
|
+
|
|
308
|
+
constraints_subsume(General, Specific) :-
|
|
309
|
+
\+ (constraint(General, Key, Value), \+ constraint(Specific, Key, Value)).
|
|
310
|
+
duties_subsume(General, Specific) :-
|
|
311
|
+
\+ (duty(General, Duty), \+ duty(Specific, Duty)).
|
|
312
|
+
|
|
313
|
+
rule_subsumes(General, Specific) :-
|
|
314
|
+
rule(General, _, Effect, GeneralParty, GeneralAction, GeneralTarget),
|
|
315
|
+
rule(Specific, _, Effect, SpecificParty, SpecificAction, SpecificTarget),
|
|
316
|
+
party_subsumes(GeneralParty, SpecificParty),
|
|
317
|
+
action_subsumes(GeneralAction, SpecificAction),
|
|
318
|
+
asset_subsumes(GeneralTarget, SpecificTarget),
|
|
319
|
+
constraints_subsume(General, Specific),
|
|
320
|
+
duties_subsume(General, Specific).
|
|
321
|
+
|
|
322
|
+
policy_subsumes(General, Specific) :-
|
|
323
|
+
policy(General, _),
|
|
324
|
+
policy(Specific, _),
|
|
325
|
+
\+ (rule(SpecificRule, Specific, _, _, _, _),
|
|
326
|
+
\+ (rule(GeneralRule, General, _, _, _, _),
|
|
327
|
+
rule_subsumes(GeneralRule, SpecificRule))).
|
|
328
|
+
|
|
329
|
+
% --- WFS: true, false, and undefined --------------------------------------
|
|
330
|
+
|
|
331
|
+
% A profile can add recursive default rules. clear is unconditionally allowed,
|
|
332
|
+
% denied has no permission, and cycle has mutually defaulted permission and
|
|
333
|
+
% prohibition. The last case is intentionally undefined under WFS.
|
|
334
|
+
profile_request(clear).
|
|
335
|
+
profile_request(denied).
|
|
336
|
+
profile_request(cycle).
|
|
337
|
+
cycle_request(cycle).
|
|
338
|
+
|
|
339
|
+
profile_permission(clear).
|
|
340
|
+
profile_permission(Request) :-
|
|
341
|
+
cycle_request(Request),
|
|
342
|
+
tnot(profile_prohibition(Request)).
|
|
343
|
+
profile_prohibition(denied).
|
|
344
|
+
profile_prohibition(Request) :-
|
|
345
|
+
cycle_request(Request),
|
|
346
|
+
tnot(profile_permission(Request)).
|
|
347
|
+
|
|
348
|
+
% For a ground WFS claim, both Claim and tnot(Claim) conditionally succeed when
|
|
349
|
+
% the truth value is undefined. That lets this example expose all three states.
|
|
350
|
+
wfs_truth(Claim, true) :- call(Claim), \+ tnot(Claim).
|
|
351
|
+
wfs_truth(Claim, false) :- tnot(Claim), \+ call(Claim).
|
|
352
|
+
wfs_truth(Claim, undefined) :- call(Claim), tnot(Claim).
|
|
353
|
+
|
|
354
|
+
% --- Curated questions -----------------------------------------------------
|
|
355
|
+
|
|
356
|
+
% All six action relationship outcomes used by the evaluator.
|
|
357
|
+
actionQuestion(exact, use, use, Kind) :- action_match(use, use, Kind).
|
|
358
|
+
actionQuestion(broader, use, print, Kind) :- action_match(use, print, Kind).
|
|
359
|
+
actionQuestion(narrower, print, use, Kind) :- action_match(print, use, Kind).
|
|
360
|
+
actionQuestion(required, read, aggregate, Kind) :- action_match(read, aggregate, Kind).
|
|
361
|
+
actionQuestion(requiring, aggregate, read, Kind) :- action_match(aggregate, read, Kind).
|
|
362
|
+
actionQuestion(unrelated, display, delete, Kind) :- action_match(display, delete, Kind).
|
|
363
|
+
|
|
364
|
+
% Rule-level evaluation shows active permission/prohibition, inactive rules
|
|
365
|
+
% caused by a failed condition or constraint, and non-applicability.
|
|
366
|
+
ruleQuestion(unconditional, open_use, q_print, Result) :- rule_result(open_use, q_print, Result).
|
|
367
|
+
ruleQuestion(duty_satisfied, share_report, q_share_done, Result) :- rule_result(share_report, q_share_done, Result).
|
|
368
|
+
ruleQuestion(duty_missing, share_report, q_share_missing, Result) :- rule_result(share_report, q_share_missing, Result).
|
|
369
|
+
ruleQuestion(constraint_true, research_use, q_research, Result) :- rule_result(research_use, q_research, Result).
|
|
370
|
+
ruleQuestion(constraint_false, research_use, q_commercial, Result) :- rule_result(research_use, q_commercial, Result).
|
|
371
|
+
ruleQuestion(scope_miss, research_use, q_unrelated, Result) :- rule_result(research_use, q_unrelated, Result).
|
|
372
|
+
ruleQuestion(prohibition, open_no_print, q_print, Result) :- rule_result(open_no_print, q_print, Result).
|
|
373
|
+
|
|
374
|
+
% The same conflicting request under all three ODRL conflict strategies, then
|
|
375
|
+
% duty/constraint failures and open-vs-closed handling when no rule applies.
|
|
376
|
+
enforcementQuestion(permission_overrides, open_printing, q_print, closed, Result, Decision) :-
|
|
377
|
+
policy_result(open_printing, q_print, Result), access_decision(open_printing, q_print, closed, Decision).
|
|
378
|
+
enforcementQuestion(prohibition_overrides, strict_printing, q_print, closed, Result, Decision) :-
|
|
379
|
+
policy_result(strict_printing, q_print, Result), access_decision(strict_printing, q_print, closed, Decision).
|
|
380
|
+
enforcementQuestion(conflict_invalidates, invalid_printing, q_print, closed, Result, Decision) :-
|
|
381
|
+
policy_result(invalid_printing, q_print, Result), access_decision(invalid_printing, q_print, closed, Decision).
|
|
382
|
+
enforcementQuestion(duty_satisfied, duty_policy, q_share_done, closed, Result, Decision) :-
|
|
383
|
+
policy_result(duty_policy, q_share_done, Result), access_decision(duty_policy, q_share_done, closed, Decision).
|
|
384
|
+
enforcementQuestion(duty_missing, duty_policy, q_share_missing, closed, Result, Decision) :-
|
|
385
|
+
policy_result(duty_policy, q_share_missing, Result), access_decision(duty_policy, q_share_missing, closed, Decision).
|
|
386
|
+
enforcementQuestion(constraint_false, context_policy, q_commercial, closed, Result, Decision) :-
|
|
387
|
+
policy_result(context_policy, q_commercial, Result), access_decision(context_policy, q_commercial, closed, Decision).
|
|
388
|
+
enforcementQuestion(no_match_closed, context_policy, q_unrelated, closed, Result, Decision) :-
|
|
389
|
+
policy_result(context_policy, q_unrelated, Result), access_decision(context_policy, q_unrelated, closed, Decision).
|
|
390
|
+
enforcementQuestion(no_match_open, context_policy, q_unrelated, open, Result, Decision) :-
|
|
391
|
+
policy_result(context_policy, q_unrelated, Result), access_decision(context_policy, q_unrelated, open, Decision).
|
|
392
|
+
enforcementQuestion(no_match_default, context_policy, q_unrelated, default, Result, Decision) :-
|
|
393
|
+
policy_result(context_policy, q_unrelated, Result), access_decision(context_policy, q_unrelated, default, Decision).
|
|
394
|
+
|
|
395
|
+
% Conflict analysis distinguishes equality, explicit action subsumption, and
|
|
396
|
+
% implicit requires dependencies.
|
|
397
|
+
conflictQuestion(exact, analysis_policy, exact_permit, exact_prohibit, Kind) :- rule_conflict(exact_permit, exact_prohibit, Kind).
|
|
398
|
+
conflictQuestion(subsumption, analysis_policy, broad_permit, narrow_prohibit, Kind) :- rule_conflict(broad_permit, narrow_prohibit, Kind).
|
|
399
|
+
conflictQuestion(dependency, analysis_policy, aggregate_permit, read_prohibit, Kind) :- rule_conflict(aggregate_permit, read_prohibit, Kind).
|
|
400
|
+
|
|
401
|
+
% Positive and negative action, rule, and whole-policy subsumption questions.
|
|
402
|
+
subsumptionQuestion(action_yes, use, display, yes) :- action_subsumes(use, display).
|
|
403
|
+
subsumptionQuestion(action_no, display, use, no) :- \+ action_subsumes(display, use).
|
|
404
|
+
subsumptionQuestion(rule_yes, general_use, specific_display, yes) :- rule_subsumes(general_use, specific_display).
|
|
405
|
+
subsumptionQuestion(rule_no, specific_display, general_use, no) :- \+ rule_subsumes(specific_display, general_use).
|
|
406
|
+
subsumptionQuestion(policy_yes, general_policy, specific_policy, yes) :- policy_subsumes(general_policy, specific_policy).
|
|
407
|
+
subsumptionQuestion(policy_no, specific_policy, general_policy, no) :- \+ policy_subsumes(specific_policy, general_policy).
|
|
408
|
+
|
|
409
|
+
wfsQuestion(clear_permission, State) :- wfs_truth(profile_permission(clear), State).
|
|
410
|
+
wfsQuestion(absent_permission, State) :- wfs_truth(profile_permission(denied), State).
|
|
411
|
+
wfsQuestion(negative_cycle, State) :- wfs_truth(profile_permission(cycle), State).
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
actionQuestion(exact, use, use, exact).
|
|
2
|
+
actionQuestion(broader, use, print, broader).
|
|
3
|
+
actionQuestion(narrower, print, use, narrower).
|
|
4
|
+
actionQuestion(required, read, aggregate, required).
|
|
5
|
+
actionQuestion(requiring, aggregate, read, requiring).
|
|
6
|
+
actionQuestion(unrelated, display, delete, no_match).
|
|
7
|
+
ruleQuestion(unconditional, open_use, q_print, permission).
|
|
8
|
+
ruleQuestion(duty_satisfied, share_report, q_share_done, permission).
|
|
9
|
+
ruleQuestion(duty_missing, share_report, q_share_missing, inactive(duty(attribute_source))).
|
|
10
|
+
ruleQuestion(constraint_true, research_use, q_research, permission).
|
|
11
|
+
ruleQuestion(constraint_false, research_use, q_commercial, inactive(constraint(purpose))).
|
|
12
|
+
ruleQuestion(scope_miss, research_use, q_unrelated, not_applicable).
|
|
13
|
+
ruleQuestion(prohibition, open_no_print, q_print, prohibition).
|
|
14
|
+
enforcementQuestion(permission_overrides, open_printing, q_print, closed, permission, permit).
|
|
15
|
+
enforcementQuestion(prohibition_overrides, strict_printing, q_print, closed, prohibition, deny).
|
|
16
|
+
enforcementQuestion(conflict_invalidates, invalid_printing, q_print, closed, invalid, invalid).
|
|
17
|
+
enforcementQuestion(duty_satisfied, duty_policy, q_share_done, closed, permission, permit).
|
|
18
|
+
enforcementQuestion(duty_missing, duty_policy, q_share_missing, closed, inactive, deny).
|
|
19
|
+
enforcementQuestion(constraint_false, context_policy, q_commercial, closed, inactive, deny).
|
|
20
|
+
enforcementQuestion(no_match_closed, context_policy, q_unrelated, closed, not_applicable, deny).
|
|
21
|
+
enforcementQuestion(no_match_open, context_policy, q_unrelated, open, not_applicable, permit).
|
|
22
|
+
enforcementQuestion(no_match_default, context_policy, q_unrelated, default, not_applicable, deny).
|
|
23
|
+
conflictQuestion(exact, analysis_policy, exact_permit, exact_prohibit, exact).
|
|
24
|
+
conflictQuestion(subsumption, analysis_policy, broad_permit, narrow_prohibit, subsumption).
|
|
25
|
+
conflictQuestion(dependency, analysis_policy, aggregate_permit, read_prohibit, dependency).
|
|
26
|
+
subsumptionQuestion(action_yes, use, display, yes).
|
|
27
|
+
subsumptionQuestion(action_no, display, use, no).
|
|
28
|
+
subsumptionQuestion(rule_yes, general_use, specific_display, yes).
|
|
29
|
+
subsumptionQuestion(rule_no, specific_display, general_use, no).
|
|
30
|
+
subsumptionQuestion(policy_yes, general_policy, specific_policy, yes).
|
|
31
|
+
subsumptionQuestion(policy_no, specific_policy, general_policy, no).
|
|
32
|
+
wfsQuestion(clear_permission, true).
|
|
33
|
+
wfsQuestion(absent_permission, false).
|
|
34
|
+
wfsQuestion(negative_cycle, undefined).
|
package/package.json
CHANGED
package/playground.html
CHANGED
package/the-art-of-eyeprolog.md
CHANGED
|
@@ -6951,7 +6951,7 @@ Review questions:
|
|
|
6951
6951
|
</figure>
|
|
6952
6952
|
|
|
6953
6953
|
The [examples directory](https://github.com/eyereasoner/eyeprolog/tree/main/examples/) is the book's executable companion. The
|
|
6954
|
-
top-level directory contains **
|
|
6954
|
+
top-level directory contains **211 self-contained runnable programs**. Every
|
|
6955
6955
|
source program has an exact answer file under
|
|
6956
6956
|
[examples/output](https://github.com/eyereasoner/eyeprolog/tree/main/examples/output/), and **61 selected programs** have a checked
|
|
6957
6957
|
explanation under [examples/proof](https://github.com/eyereasoner/eyeprolog/tree/main/examples/proof/). The thematic tables below link every top-level program and open the program
|
|
@@ -7281,6 +7281,7 @@ ground RDF-shaped results that can be serialized back to RDF.
|
|
|
7281
7281
|
| [ODRL–DPV consumer risk ranking](https://github.com/eyereasoner/eyeprolog/blob/main/examples/odrl-dpv-risk-ranked.pl) | Score consumer-policy conflicts and return a deterministic risk ranking. | [answers](https://github.com/eyereasoner/eyeprolog/blob/main/examples/output/odrl-dpv-risk-ranked.pl) |
|
|
7282
7282
|
| [ODRL policy](https://github.com/eyereasoner/eyeprolog/blob/main/examples/odrl-policy.pl) | Read one purpose-constrained permission from an ODRL policy graph. | [answers](https://github.com/eyereasoner/eyeprolog/blob/main/examples/output/odrl-policy.pl) |
|
|
7283
7283
|
| [Advanced ODRL policy](https://github.com/eyereasoner/eyeprolog/blob/main/examples/odrl-policy-advanced.pl) | Evaluate permission, duty, constraint failure, and prohibition outcomes. | [answers](https://github.com/eyereasoner/eyeprolog/blob/main/examples/output/odrl-policy-advanced.pl) |
|
|
7284
|
+
| [ODRL policy reasoning](https://github.com/eyereasoner/eyeprolog/blob/main/examples/odrl-policy-reasoning.pl) | Query action relationships, rule and enforcement outcomes, conflict strategies and kinds, action/rule/policy subsumption, and three-valued WFS defaults. | [answers](https://github.com/eyereasoner/eyeprolog/blob/main/examples/output/odrl-policy-reasoning.pl) |
|
|
7284
7285
|
| [RDF 1.2 annotated claims](https://github.com/eyereasoner/eyeprolog/blob/main/examples/rdf12-annotated-claims.pl) | Rank conflicting annotated claims by confidence and source trust. | [answers](https://github.com/eyereasoner/eyeprolog/blob/main/examples/output/rdf12-annotated-claims.pl) |
|
|
7285
7286
|
| [RDF 1.2 annotation](https://github.com/eyereasoner/eyeprolog/blob/main/examples/rdf12-annotation.pl) | Recover an asserted triple together with its reifier and annotations. | [answers](https://github.com/eyereasoner/eyeprolog/blob/main/examples/output/rdf12-annotation.pl) |
|
|
7286
7287
|
| [RDF 1.2 directional language](https://github.com/eyereasoner/eyeprolog/blob/main/examples/rdf12-directional-language.pl) | Preserve language and base-direction metadata in derived labels. | [answers](https://github.com/eyereasoner/eyeprolog/blob/main/examples/output/rdf12-directional-language.pl) |
|