kibi-core 0.7.1 → 0.9.1
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.
- package/package.json +1 -1
- package/schema/entities.pl +28 -0
- package/schema/relationships.pl +2 -0
- package/schema/validation.pl +41 -32
- package/src/checks.pl +410 -19
- package/src/discovery.pl +88 -18
- package/src/kb.pl +256 -19
- package/src/logic_ir.pl +629 -0
- package/src/requirement_proof.pl +837 -0
package/src/logic_ir.pl
ADDED
|
@@ -0,0 +1,629 @@
|
|
|
1
|
+
% Safe, data-driven interpreter for kibi.logic.v1.
|
|
2
|
+
%
|
|
3
|
+
% This module deliberately parses a small JSON IR into ordinary Prolog terms.
|
|
4
|
+
% It never consults or asserts caller-provided source. The terms are evaluated
|
|
5
|
+
% by a bounded interpreter and are suitable for proof witnesses.
|
|
6
|
+
:- module(logic_ir, [
|
|
7
|
+
logic_rule_from_props/2,
|
|
8
|
+
logic_rule_safety/2,
|
|
9
|
+
logic_rule_render/2,
|
|
10
|
+
logic_rule_semantic_key/2,
|
|
11
|
+
logic_derive/2,
|
|
12
|
+
logic_rule_conflict/3,
|
|
13
|
+
logic_rule_conflict_witness/3,
|
|
14
|
+
logic_atom_signature/2,
|
|
15
|
+
logic_rules_stratified/1
|
|
16
|
+
]).
|
|
17
|
+
|
|
18
|
+
:- use_module(library(http/json)).
|
|
19
|
+
:- use_module(library(aggregate)).
|
|
20
|
+
:- use_module(library(clpfd)).
|
|
21
|
+
:- use_module('kb.pl', [kb_entity/3, predicate_fact/5]).
|
|
22
|
+
|
|
23
|
+
%% logic_rule_from_props(+Props, -Rule)
|
|
24
|
+
% Props is an entity property list from a fact_kind=rule fact.
|
|
25
|
+
logic_rule_from_props(Props, Rule) :-
|
|
26
|
+
memberchk(rule_ir=Raw, Props),
|
|
27
|
+
raw_json_atom(Raw, Json),
|
|
28
|
+
catch(atom_json_dict(Json, Dict, [value_string_as(atom)]), _, fail),
|
|
29
|
+
json_rule(Dict, Rule).
|
|
30
|
+
|
|
31
|
+
raw_json_atom(^^(Value, _), Atom) :- !, raw_json_atom(Value, Atom).
|
|
32
|
+
raw_json_atom(literal(type(_, Value)), Atom) :- !, raw_json_atom(Value, Atom).
|
|
33
|
+
raw_json_atom(Value, Atom) :- atom(Value), !, Atom = Value.
|
|
34
|
+
raw_json_atom(Value, Atom) :- string(Value), !, atom_string(Atom, Value).
|
|
35
|
+
|
|
36
|
+
json_rule(Dict, rule(Kind, Modality, Head, Body, Exceptions, Scope, From, To, SchemaId, Variables)) :-
|
|
37
|
+
json_rule_keys(Dict),
|
|
38
|
+
get_dict(version, Dict, Version),
|
|
39
|
+
json_text(Version, 'kibi.logic.v1'),
|
|
40
|
+
json_text(Dict.kind, Kind),
|
|
41
|
+
json_modality(Dict.modality, Modality),
|
|
42
|
+
( get_dict(head, Dict, HeadDict) -> json_atom(HeadDict, Head) ; Head = none ),
|
|
43
|
+
( get_dict(body, Dict, BodyDict) -> json_expression(BodyDict, Body) ; Body = none ),
|
|
44
|
+
( get_dict(exceptions, Dict, ExceptionDicts) -> maplist(json_expression, ExceptionDicts, Exceptions) ; Exceptions = [] ),
|
|
45
|
+
( get_dict(scope, Dict, ScopeDict) -> json_scope(ScopeDict, Scope) ; Scope = scope('', '', []) ),
|
|
46
|
+
( get_dict(validFrom, Dict, RawFrom) -> json_timestamp(RawFrom, From) ; From = '' ),
|
|
47
|
+
( get_dict(validTo, Dict, RawTo) -> json_timestamp(RawTo, To) ; To = '' ),
|
|
48
|
+
( get_dict(ruleSchemaId, Dict, RawSchemaId) -> json_atom_name(RawSchemaId, SchemaId), valid_schema_id(SchemaId) ; SchemaId = '' ),
|
|
49
|
+
( get_dict(variables, Dict, RawVariables) -> maplist(json_variable, RawVariables, Variables) ; Variables = [] ).
|
|
50
|
+
|
|
51
|
+
json_rule_keys(Dict) :-
|
|
52
|
+
dict_pairs(Dict, _Tag, Pairs),
|
|
53
|
+
forall(member(Key-_, Pairs), memberchk(Key, [version, kind, modality, head, body, variables, exceptions, scope, validFrom, validTo, ruleSchemaId])).
|
|
54
|
+
|
|
55
|
+
json_variable(Dict, variable(Name, Type, Quantifier)) :-
|
|
56
|
+
is_dict(Dict),
|
|
57
|
+
dict_keys_allowed(Dict, [name, type, quantifier]),
|
|
58
|
+
get_dict(name, Dict, RawName), json_atom_name(RawName, Name),
|
|
59
|
+
valid_variable_name(Name),
|
|
60
|
+
get_dict(type, Dict, RawType), json_atom_name(RawType, Type), valid_type_name(Type),
|
|
61
|
+
( get_dict(quantifier, Dict, RawQuantifier) -> json_quantifier(RawQuantifier, Quantifier) ; Quantifier = forall ).
|
|
62
|
+
|
|
63
|
+
json_quantifier(Value, Value) :- memberchk(Value, [forall, exists]), !.
|
|
64
|
+
json_quantifier(Value, Quantifier) :- string(Value), atom_string(Atom, Value), json_quantifier(Atom, Quantifier).
|
|
65
|
+
|
|
66
|
+
json_modality(Value, Value) :- memberchk(Value, [assert, deny, oblige, permit, forbid]), !.
|
|
67
|
+
json_modality(Value, Value) :- string(Value), atom_string(Atom, Value), json_modality(Atom, Value).
|
|
68
|
+
|
|
69
|
+
json_atom(Dict, atom(Namespace, Name, Args, Polarity, ClosedWorld)) :-
|
|
70
|
+
is_dict(Dict),
|
|
71
|
+
dict_keys_allowed(Dict, [kind, namespace, name, args, polarity, closedWorld]),
|
|
72
|
+
get_dict(kind, Dict, Kind),
|
|
73
|
+
json_text(Kind, atom),
|
|
74
|
+
get_dict(name, Dict, RawName),
|
|
75
|
+
json_atom_name(RawName, Name), valid_predicate_name(Name),
|
|
76
|
+
( get_dict(namespace, Dict, RawNamespace) -> json_atom_name(RawNamespace, Namespace), valid_predicate_name(Namespace) ; Namespace = default ),
|
|
77
|
+
get_dict(args, Dict, RawArgs),
|
|
78
|
+
is_list(RawArgs),
|
|
79
|
+
length(RawArgs, Arity), Arity =< 8,
|
|
80
|
+
maplist(json_term, RawArgs, Args),
|
|
81
|
+
( get_dict(polarity, Dict, RawPolarity) -> json_polarity(RawPolarity, Polarity) ; Polarity = positive ),
|
|
82
|
+
( get_dict(closedWorld, Dict, RawClosedWorld) -> memberchk(RawClosedWorld, [true, false]), ClosedWorld = RawClosedWorld ; ClosedWorld = false ).
|
|
83
|
+
|
|
84
|
+
json_atom_name(Value, Atom) :- atom(Value), !, Atom = Value.
|
|
85
|
+
json_atom_name(Value, Atom) :- string(Value), !, atom_string(Atom, Value).
|
|
86
|
+
|
|
87
|
+
valid_variable_name(Name) :-
|
|
88
|
+
atom(Name),
|
|
89
|
+
atom_chars(Name, [First|Rest]),
|
|
90
|
+
char_type(First, upper),
|
|
91
|
+
forall(member(Char, Rest), (char_type(Char, alnum) ; Char == '_')).
|
|
92
|
+
|
|
93
|
+
valid_type_name(Name) :-
|
|
94
|
+
atom(Name),
|
|
95
|
+
atom_chars(Name, [First|Rest]),
|
|
96
|
+
char_type(First, lower),
|
|
97
|
+
forall(member(Char, Rest), (char_type(Char, alnum) ; memberchk(Char, ['_', '-', ':', '/', '.']))).
|
|
98
|
+
|
|
99
|
+
valid_schema_id(Name) :-
|
|
100
|
+
atom(Name),
|
|
101
|
+
atom_chars(Name, [First|Rest]),
|
|
102
|
+
char_type(First, alnum),
|
|
103
|
+
forall(member(Char, Rest), (char_type(Char, alnum) ; memberchk(Char, ['_', '-', ':', '/', '.']))).
|
|
104
|
+
|
|
105
|
+
json_timestamp(Value, Timestamp) :-
|
|
106
|
+
json_atom_name(Value, Timestamp),
|
|
107
|
+
parse_time(Timestamp, iso_8601, _).
|
|
108
|
+
|
|
109
|
+
valid_predicate_name(Name) :-
|
|
110
|
+
atom(Name),
|
|
111
|
+
atom_chars(Name, [First|Rest]),
|
|
112
|
+
char_type(First, lower),
|
|
113
|
+
forall(member(Char, Rest), allowed_predicate_char(Char)).
|
|
114
|
+
|
|
115
|
+
allowed_predicate_char(Char) :- char_type(Char, alnum), !.
|
|
116
|
+
allowed_predicate_char('_').
|
|
117
|
+
allowed_predicate_char('-').
|
|
118
|
+
allowed_predicate_char('/').
|
|
119
|
+
allowed_predicate_char('.').
|
|
120
|
+
|
|
121
|
+
json_text(Value, Expected) :- atom(Value), !, Value = Expected.
|
|
122
|
+
json_text(Value, Expected) :- string(Value), atom_string(Expected, Value).
|
|
123
|
+
|
|
124
|
+
json_polarity(Value, Value) :- memberchk(Value, [positive, negative]), !.
|
|
125
|
+
json_polarity(Value, Polarity) :- string(Value), atom_string(Atom, Value), json_polarity(Atom, Polarity).
|
|
126
|
+
|
|
127
|
+
json_term(Dict, var(Name, Type)) :-
|
|
128
|
+
is_dict(Dict), dict_keys_allowed(Dict, [kind, name, type]), get_dict(kind, Dict, Kind), json_text(Kind, var),
|
|
129
|
+
get_dict(name, Dict, RawName), json_atom_name(RawName, Name), valid_variable_name(Name),
|
|
130
|
+
get_dict(type, Dict, RawType), json_atom_name(RawType, Type), valid_type_name(Type), !.
|
|
131
|
+
json_term(Dict, const(Value, Type)) :-
|
|
132
|
+
is_dict(Dict), dict_keys_allowed(Dict, [kind, value, type]), get_dict(kind, Dict, Kind), json_text(Kind, const),
|
|
133
|
+
get_dict(value, Dict, RawValue), json_atom_name(RawValue, Value),
|
|
134
|
+
( get_dict(type, Dict, RawType) -> json_atom_name(RawType, Type) ; Type = string ), !.
|
|
135
|
+
json_term(Dict, number(Value, Unit)) :-
|
|
136
|
+
is_dict(Dict), dict_keys_allowed(Dict, [kind, value, unit]), get_dict(kind, Dict, Kind), json_text(Kind, number),
|
|
137
|
+
get_dict(value, Dict, Value), number(Value), Value =:= Value,
|
|
138
|
+
( get_dict(unit, Dict, RawUnit) -> json_atom_name(RawUnit, Unit), valid_type_name(Unit) ; Unit = none ), !.
|
|
139
|
+
json_term(Dict, duration(Value, Unit)) :-
|
|
140
|
+
is_dict(Dict), dict_keys_allowed(Dict, [kind, value, unit]), get_dict(kind, Dict, Kind), json_text(Kind, duration),
|
|
141
|
+
get_dict(value, Dict, Value), number(Value), Value =:= Value, Value >= 0,
|
|
142
|
+
get_dict(unit, Dict, RawUnit), json_atom_name(RawUnit, Unit), memberchk(Unit, [ms, s, m, h, d, w]), !.
|
|
143
|
+
json_term(Dict, timestamp(Value)) :-
|
|
144
|
+
is_dict(Dict), dict_keys_allowed(Dict, [kind, value]), get_dict(kind, Dict, Kind), json_text(Kind, timestamp),
|
|
145
|
+
get_dict(value, Dict, RawValue), json_timestamp(RawValue, Value), !.
|
|
146
|
+
json_term(Dict, interval(Start, End)) :-
|
|
147
|
+
is_dict(Dict), dict_keys_allowed(Dict, [kind, start, end]), get_dict(kind, Dict, Kind), json_text(Kind, interval),
|
|
148
|
+
get_dict(start, Dict, RawStart), json_timestamp(RawStart, Start),
|
|
149
|
+
get_dict(end, Dict, RawEnd), json_timestamp(RawEnd, End), Start @=< End, !.
|
|
150
|
+
|
|
151
|
+
json_expression(Dict, Atom) :- is_dict(Dict), dict_keys_allowed(Dict, [kind, namespace, name, args, polarity, closedWorld]), get_dict(kind, Dict, Kind), json_text(Kind, atom), !, json_atom(Dict, Atom).
|
|
152
|
+
json_expression(Dict, all(Items)) :- is_dict(Dict), dict_keys_allowed(Dict, [kind, items]), get_dict(kind, Dict, Kind), json_text(Kind, all), !, get_dict(items, Dict, Raw), is_list(Raw), Raw \= [], length(Raw, Count), Count =< 32, maplist(json_expression, Raw, Items).
|
|
153
|
+
json_expression(Dict, any(Items)) :- is_dict(Dict), dict_keys_allowed(Dict, [kind, items]), get_dict(kind, Dict, Kind), json_text(Kind, any), !, get_dict(items, Dict, Raw), is_list(Raw), Raw \= [], length(Raw, Count), Count =< 32, maplist(json_expression, Raw, Items).
|
|
154
|
+
json_expression(Dict, not(Atom)) :- is_dict(Dict), dict_keys_allowed(Dict, [kind, item]), get_dict(kind, Dict, Kind), json_text(Kind, not), !, get_dict(item, Dict, Raw), json_atom(Raw, Atom).
|
|
155
|
+
json_expression(Dict, compare(Op, Left, Right)) :- is_dict(Dict), dict_keys_allowed(Dict, [kind, operator, left, right]), get_dict(kind, Dict, Kind), json_text(Kind, compare), !, get_dict(operator, Dict, OpRaw), json_atom_name(OpRaw, Op), valid_compare_operator(Op), get_dict(left, Dict, LeftRaw), get_dict(right, Dict, RightRaw), json_term(LeftRaw, Left), json_term(RightRaw, Right).
|
|
156
|
+
json_expression(Dict, count(Atom, Op, Value)) :- is_dict(Dict), dict_keys_allowed(Dict, [kind, atom, operator, value]), get_dict(kind, Dict, Kind), json_text(Kind, count), !, get_dict(atom, Dict, AtomRaw), json_atom(AtomRaw, Atom), get_dict(operator, Dict, OpRaw), json_atom_name(OpRaw, Op), valid_compare_operator(Op), get_dict(value, Dict, Value), integer(Value), Value >= 0.
|
|
157
|
+
json_expression(Dict, temporal(Relation, Left, Right)) :- is_dict(Dict), dict_keys_allowed(Dict, [kind, relation, left, right]), get_dict(kind, Dict, Kind), json_text(Kind, temporal), !, get_dict(relation, Dict, RelationRaw), json_atom_name(RelationRaw, Relation), valid_temporal_relation(Relation), get_dict(left, Dict, LeftRaw), get_dict(right, Dict, RightRaw), json_term(LeftRaw, Left), json_term(RightRaw, Right).
|
|
158
|
+
|
|
159
|
+
valid_compare_operator(Op) :- memberchk(Op, [eq, neq, lt, lte, gt, gte]).
|
|
160
|
+
valid_temporal_relation(Relation) :- memberchk(Relation, [before, after, during, overlaps, starts, finishes]).
|
|
161
|
+
|
|
162
|
+
json_scope(Dict, scope(Authority, Name, Tags)) :-
|
|
163
|
+
dict_keys_allowed(Dict, [authority, name, tags]),
|
|
164
|
+
( get_dict(authority, Dict, Authority0) -> json_atom_name(Authority0, Authority) ; Authority = '' ),
|
|
165
|
+
( get_dict(name, Dict, Name0) -> json_atom_name(Name0, Name) ; Name = '' ),
|
|
166
|
+
( get_dict(tags, Dict, Tags0) -> maplist(json_atom_name, Tags0, Tags) ; Tags = [] ).
|
|
167
|
+
|
|
168
|
+
dict_keys_allowed(Dict, Allowed) :-
|
|
169
|
+
dict_pairs(Dict, _Tag, Pairs),
|
|
170
|
+
forall(member(Key-_, Pairs), memberchk(Key, Allowed)).
|
|
171
|
+
|
|
172
|
+
%% logic_rule_safety(+Props, -Errors)
|
|
173
|
+
% Independent Prolog-side safety check. It intentionally rejects anything
|
|
174
|
+
% that the JSON decoder cannot map into the closed IR vocabulary.
|
|
175
|
+
logic_rule_safety(Props, Errors) :-
|
|
176
|
+
( logic_rule_from_props(Props, Rule)
|
|
177
|
+
-> rule_safety_errors(Rule, Errors)
|
|
178
|
+
; Errors = [malformed_rule_ir]
|
|
179
|
+
).
|
|
180
|
+
|
|
181
|
+
rule_safety_errors(rule(Kind, _Modality, Head, Body, Exceptions, _Scope, _From, _To, _Schema, Variables), Errors) :-
|
|
182
|
+
findall(Error, rule_shape_error(Kind, Head, Body, Error), ShapeErrors),
|
|
183
|
+
findall(Error, expression_safety_error(Body, Error), BodyErrors),
|
|
184
|
+
findall(Error, (member(Exception, Exceptions), expression_safety_error(Exception, Error)), ExceptionErrors),
|
|
185
|
+
findall(Error, variable_safety_error(Head, Body, Variables, Error), VariableErrors),
|
|
186
|
+
findall(Error, undeclared_variable_error(Head, Body, Exceptions, Variables, Error), DeclarationErrors),
|
|
187
|
+
findall(Error, (member(Exception, Exceptions), exception_variable_safety_error(Exception, Body, Error)), ExceptionVariableErrors),
|
|
188
|
+
findall(Error, rule_term_safety_error(Head, Body, Exceptions, Error), TermErrors),
|
|
189
|
+
findall(Error, rule_limit_error(Head, Body, Exceptions, Error), LimitErrors),
|
|
190
|
+
append([ShapeErrors, BodyErrors, ExceptionErrors, VariableErrors, DeclarationErrors, ExceptionVariableErrors, TermErrors, LimitErrors], Errors0),
|
|
191
|
+
sort(Errors0, Errors).
|
|
192
|
+
|
|
193
|
+
rule_term_safety_error(Head, Body, Exceptions, invalid_interval) :-
|
|
194
|
+
member(Term, [Head, Body|Exceptions]),
|
|
195
|
+
expression_term(Term, interval(Start, End)),
|
|
196
|
+
Start @> End.
|
|
197
|
+
|
|
198
|
+
rule_limit_error(Head, Body, Exceptions, too_many_atoms) :-
|
|
199
|
+
expression_atom_count(Head, HeadCount),
|
|
200
|
+
expression_atom_count(Body, BodyCount),
|
|
201
|
+
maplist(expression_atom_count, Exceptions, ExceptionCounts),
|
|
202
|
+
sum_list([HeadCount, BodyCount|ExceptionCounts], Count),
|
|
203
|
+
Count > 64.
|
|
204
|
+
rule_limit_error(Head, Body, Exceptions, expression_depth_exceeded) :-
|
|
205
|
+
member(Term, [Head, Body|Exceptions]),
|
|
206
|
+
expression_depth(Term, Depth),
|
|
207
|
+
Depth > 16.
|
|
208
|
+
|
|
209
|
+
expression_atom_count(none, 0).
|
|
210
|
+
expression_atom_count(atom(_, _, _, _, _), 1).
|
|
211
|
+
expression_atom_count(all(Items), Count) :- maplist(expression_atom_count, Items, Counts), sum_list(Counts, ChildCount), Count is ChildCount.
|
|
212
|
+
expression_atom_count(any(Items), Count) :- maplist(expression_atom_count, Items, Counts), sum_list(Counts, ChildCount), Count is ChildCount.
|
|
213
|
+
expression_atom_count(not(Atom), Count) :- expression_atom_count(Atom, ChildCount), Count is ChildCount.
|
|
214
|
+
expression_atom_count(compare(_, _, _), 0).
|
|
215
|
+
expression_atom_count(count(Atom, _, _), Count) :- expression_atom_count(Atom, Count).
|
|
216
|
+
expression_atom_count(temporal(_, _, _), 0).
|
|
217
|
+
|
|
218
|
+
expression_depth(none, 0).
|
|
219
|
+
expression_depth(atom(_, _, _, _, _), 1).
|
|
220
|
+
expression_depth(all(Items), Depth) :- maplist(expression_depth, Items, Depths), max_list([0|Depths], ChildDepth), Depth is ChildDepth + 1.
|
|
221
|
+
expression_depth(any(Items), Depth) :- maplist(expression_depth, Items, Depths), max_list([0|Depths], ChildDepth), Depth is ChildDepth + 1.
|
|
222
|
+
expression_depth(not(Atom), Depth) :- expression_depth(Atom, ChildDepth), Depth is ChildDepth + 1.
|
|
223
|
+
expression_depth(compare(_, _, _), 1).
|
|
224
|
+
expression_depth(count(Atom, _, _), Depth) :- expression_depth(Atom, ChildDepth), Depth is ChildDepth + 1.
|
|
225
|
+
expression_depth(temporal(_, _, _), 1).
|
|
226
|
+
|
|
227
|
+
expression_term(atom(_, _, Args, _, _), Term) :- member(Term, Args).
|
|
228
|
+
expression_term(all(Items), Term) :- member(Item, Items), expression_term(Item, Term).
|
|
229
|
+
expression_term(any(Items), Term) :- member(Item, Items), expression_term(Item, Term).
|
|
230
|
+
expression_term(not(Atom), Term) :- expression_term(Atom, Term).
|
|
231
|
+
expression_term(compare(_, Left, Right), Term) :- member(Term, [Left, Right]).
|
|
232
|
+
expression_term(count(Atom, _, _), Term) :- expression_term(Atom, Term).
|
|
233
|
+
expression_term(temporal(_, Left, Right), Term) :- member(Term, [Left, Right]).
|
|
234
|
+
expression_term(Term, Term) :- Term = interval(_, _).
|
|
235
|
+
|
|
236
|
+
variable_safety_error(Head, Body, _Variables, head_variable_not_range_restricted(Name)) :-
|
|
237
|
+
head_variable(Name, Head),
|
|
238
|
+
\+ positive_variable(Name, Body).
|
|
239
|
+
variable_safety_error(Head, _Body, Variables, existential_head_variable(Name)) :-
|
|
240
|
+
head_variable(Name, Head),
|
|
241
|
+
memberchk(variable(Name, _Type, exists), Variables).
|
|
242
|
+
variable_safety_error(_Head, Body, _Variables, body_variable_not_range_restricted(Name)) :-
|
|
243
|
+
body_variable(Name, Body),
|
|
244
|
+
\+ positive_variable(Name, Body).
|
|
245
|
+
|
|
246
|
+
variable_safety_error(Head, Body, Variables, existential_variable_not_body_local(Name)) :-
|
|
247
|
+
memberchk(variable(Name, _Type, exists), Variables),
|
|
248
|
+
( head_variable(Name, Head) ; \+ expression_variable(Body, Name) ).
|
|
249
|
+
|
|
250
|
+
undeclared_variable_error(Head, Body, Exceptions, Variables, undeclared_variable(Name)) :-
|
|
251
|
+
member(Expression, [Head, Body|Exceptions]),
|
|
252
|
+
expression_variable(Expression, Name),
|
|
253
|
+
\+ memberchk(variable(Name, _Type, _Quantifier), Variables).
|
|
254
|
+
|
|
255
|
+
exception_variable_safety_error(Exception, Body, exception_variable_not_range_restricted(Name)) :-
|
|
256
|
+
expression_variable(Exception, Name),
|
|
257
|
+
\+ positive_variable(Name, Body).
|
|
258
|
+
|
|
259
|
+
head_variable(Name, atom(_, _, Args, _, _)) :- member(Arg, Args), term_variable(Arg, Name).
|
|
260
|
+
body_variable(Name, Expression) :- expression_variable(Expression, Name).
|
|
261
|
+
positive_variable(Name, atom(_, _, Args, positive, _)) :- member(Arg, Args), term_variable(Arg, Name).
|
|
262
|
+
positive_variable(Name, all(Items)) :- member(Item, Items), positive_variable(Name, Item).
|
|
263
|
+
positive_variable(Name, any(Items)) :- member(Item, Items), positive_variable(Name, Item).
|
|
264
|
+
positive_variable(Name, count(Atom, _, _)) :- positive_variable(Name, Atom).
|
|
265
|
+
positive_variable(_, _) :- fail.
|
|
266
|
+
|
|
267
|
+
term_variable(var(Name, _), Name).
|
|
268
|
+
expression_variable(atom(_, _, Args, _, _), Name) :- member(Arg, Args), term_variable(Arg, Name).
|
|
269
|
+
expression_variable(all(Items), Name) :- member(Item, Items), expression_variable(Item, Name).
|
|
270
|
+
expression_variable(any(Items), Name) :- member(Item, Items), expression_variable(Item, Name).
|
|
271
|
+
expression_variable(not(Atom), Name) :- expression_variable(Atom, Name).
|
|
272
|
+
expression_variable(compare(_, Left, Right), Name) :- (term_variable(Left, Name) ; term_variable(Right, Name)).
|
|
273
|
+
expression_variable(count(Atom, _, _), Name) :- expression_variable(Atom, Name).
|
|
274
|
+
expression_variable(temporal(_, Left, Right), Name) :- (term_variable(Left, Name) ; term_variable(Right, Name)).
|
|
275
|
+
|
|
276
|
+
rule_shape_error(atom, none, _Body, missing_head).
|
|
277
|
+
rule_shape_error(atom, _Head, Body, body_for_atom) :- Body \= none.
|
|
278
|
+
rule_shape_error(rule, none, _Body, missing_head).
|
|
279
|
+
rule_shape_error(rule, _Head, none, missing_body).
|
|
280
|
+
rule_shape_error(constraint, _Head, none, missing_body).
|
|
281
|
+
rule_shape_error(constraint, Head, _Body, head_for_constraint) :- Head \= none.
|
|
282
|
+
rule_shape_error(_, _, _, _) :- fail.
|
|
283
|
+
|
|
284
|
+
expression_safety_error(none, _) :- fail.
|
|
285
|
+
expression_safety_error(not(atom(_, _, _, _, false)), negation_requires_closed_world).
|
|
286
|
+
expression_safety_error(compare(Op, _Left, _Right), invalid_compare_operator) :- \+ valid_compare_operator(Op).
|
|
287
|
+
expression_safety_error(count(_Atom, Op, _Value), invalid_compare_operator) :- \+ valid_compare_operator(Op).
|
|
288
|
+
expression_safety_error(count(_Atom, _Op, Value), invalid_count_value) :- \+ integer(Value) ; Value < 0.
|
|
289
|
+
expression_safety_error(temporal(Relation, _Left, _Right), invalid_temporal_relation) :- \+ valid_temporal_relation(Relation).
|
|
290
|
+
expression_safety_error(all(Items), Error) :- member(Item, Items), expression_safety_error(Item, Error).
|
|
291
|
+
expression_safety_error(any(Items), Error) :- member(Item, Items), expression_safety_error(Item, Error).
|
|
292
|
+
expression_safety_error(not(Atom), Error) :- expression_safety_error(Atom, Error).
|
|
293
|
+
expression_safety_error(_, _) :- fail.
|
|
294
|
+
|
|
295
|
+
%% logic_rule_render(+Props, -Text)
|
|
296
|
+
logic_rule_render(Props, Text) :-
|
|
297
|
+
logic_rule_from_props(Props, Rule),
|
|
298
|
+
render_rule(Rule, Text).
|
|
299
|
+
|
|
300
|
+
render_rule(rule(_Kind, Modality, Head, none, _Exceptions, _Scope, _From, _To, _Schema, _Variables), Text) :-
|
|
301
|
+
render_head(Modality, Head, HeadText), format(string(Text), '~w.', [HeadText]).
|
|
302
|
+
render_rule(rule(_Kind, Modality, Head, Body, _Exceptions, _Scope, _From, _To, _Schema, _Variables), Text) :-
|
|
303
|
+
render_head(Modality, Head, HeadText), render_expression(Body, BodyText), format(string(Text), '~w :- ~w.', [HeadText, BodyText]).
|
|
304
|
+
|
|
305
|
+
render_head(Modality, none, Text) :- format(string(Text), '~w', [Modality]).
|
|
306
|
+
render_head(Modality, Atom, Text) :- render_atom(Atom, AtomText), format(string(Text), '~w(~w)', [Modality, AtomText]).
|
|
307
|
+
render_atom(atom(Namespace, Name, Args, Polarity, _), Text) :-
|
|
308
|
+
( Namespace == default -> Full = Name ; format(atom(Full), '~w:~w', [Namespace, Name]) ),
|
|
309
|
+
maplist(render_term, Args, Rendered), atomic_list_concat(Rendered, ',', ArgsText),
|
|
310
|
+
( Polarity == negative -> format(string(Text), 'negative(~w(~w))', [Full, ArgsText]) ; format(string(Text), '~w(~w)', [Full, ArgsText]) ).
|
|
311
|
+
render_term(var(Name, _), Name).
|
|
312
|
+
render_term(const(Value, _), Value).
|
|
313
|
+
render_term(number(Value, none), Text) :- format(string(Text), '~w', [Value]).
|
|
314
|
+
render_term(number(Value, Unit), Text) :- format(string(Text), '~w~w', [Value, Unit]).
|
|
315
|
+
render_term(duration(Value, Unit), Text) :- format(string(Text), 'duration(~w,~w)', [Value, Unit]).
|
|
316
|
+
render_term(timestamp(Value), Value).
|
|
317
|
+
render_term(interval(Start, End), Text) :- format(string(Text), 'interval(~w,~w)', [Start, End]).
|
|
318
|
+
render_expression(Atom, Text) :- Atom = atom(_, _, _, _, _), !, render_atom(Atom, Text).
|
|
319
|
+
render_expression(all(Items), Text) :- maplist(render_expression, Items, Parts), atomic_list_concat(Parts, ', ', Text).
|
|
320
|
+
render_expression(any(Items), Text) :- maplist(render_expression, Items, Parts), atomic_list_concat(Parts, ' ; ', Inner), format(string(Text), '(~w)', [Inner]).
|
|
321
|
+
render_expression(not(Atom), Text) :- render_atom(Atom, AtomText), format(string(Text), 'not(~w)', [AtomText]).
|
|
322
|
+
render_expression(compare(Op, Left, Right), Text) :- render_term(Left, L), render_term(Right, R), format(string(Text), '~w ~w ~w', [L, Op, R]).
|
|
323
|
+
render_expression(count(Atom, Op, Value), Text) :- render_atom(Atom, AtomText), format(string(Text), 'count(~w,~w,~w)', [AtomText, Op, Value]).
|
|
324
|
+
render_expression(temporal(Relation, Left, Right), Text) :- render_term(Left, L), render_term(Right, R), format(string(Text), 'temporal(~w,~w,~w)', [Relation, L, R]).
|
|
325
|
+
|
|
326
|
+
logic_rule_semantic_key(Props, Key) :-
|
|
327
|
+
memberchk(semantic_key=Key, Props), !.
|
|
328
|
+
logic_rule_semantic_key(Props, Key) :-
|
|
329
|
+
memberchk(rule_hash=Hash, Props), !, Key = Hash.
|
|
330
|
+
|
|
331
|
+
logic_atom_signature(atom(Namespace, Name, Args, Polarity, _), signature(Namespace, Name, Args, Polarity)).
|
|
332
|
+
|
|
333
|
+
%% logic_derive(+Goal, -Proof)
|
|
334
|
+
% Goal is atom(Namespace, Name, Args, Polarity), with ground arguments. Rules
|
|
335
|
+
% are interpreted by a finite, data-driven evaluator; no stored source is
|
|
336
|
+
% consulted and recursive rule chains are bounded.
|
|
337
|
+
logic_derive(Goal, Proof) :-
|
|
338
|
+
ground(Goal),
|
|
339
|
+
derive_goal(Goal, [], 0, Proof).
|
|
340
|
+
|
|
341
|
+
logic_max_depth(16).
|
|
342
|
+
|
|
343
|
+
derive_goal(Goal, _Seen, _Depth, direct(FactId)) :-
|
|
344
|
+
Goal = atom(Namespace, Name, Args, Polarity, _),
|
|
345
|
+
predicate_fact(FactId, Namespace, Name, Args, Polarity), !.
|
|
346
|
+
derive_goal(Goal, Seen, Depth, rule(FactId, BodyProof)) :-
|
|
347
|
+
logic_max_depth(MaxDepth),
|
|
348
|
+
Depth < MaxDepth,
|
|
349
|
+
logic_rule_fact(FactId, StoredRule),
|
|
350
|
+
materialize_rule(StoredRule, Rule),
|
|
351
|
+
Rule = rule(rule, _Modality, Head, Body, Exceptions, _Scope, _From, _To, _Schema, _Variables),
|
|
352
|
+
Goal = Head,
|
|
353
|
+
atom_signature_for_seen(Head, Signature),
|
|
354
|
+
\+ memberchk(Signature, Seen),
|
|
355
|
+
NextDepth is Depth + 1,
|
|
356
|
+
prove_expression(Body, [Signature|Seen], NextDepth, BodyProof),
|
|
357
|
+
exceptions_do_not_hold(Exceptions, [Signature|Seen], NextDepth).
|
|
358
|
+
|
|
359
|
+
logic_rule_fact(FactId, Rule) :-
|
|
360
|
+
kb_entity(FactId, fact, Props),
|
|
361
|
+
memberchk(fact_kind=Kind, Props), normalize_atom(Kind, rule),
|
|
362
|
+
logic_rule_from_props(Props, Rule).
|
|
363
|
+
|
|
364
|
+
% The JSON decoder keeps variable names as data terms for validation and
|
|
365
|
+
% rendering. Evaluation reifies those names into fresh Prolog variables so
|
|
366
|
+
% ordinary unification can be used without ever evaluating caller source.
|
|
367
|
+
materialize_rule(
|
|
368
|
+
rule(Kind, Modality, Head, Body, Exceptions, Scope, From, To, Schema, Variables),
|
|
369
|
+
rule(Kind, Modality, MaterialHead, MaterialBody, MaterialExceptions, Scope, From, To, Schema, Variables)
|
|
370
|
+
) :-
|
|
371
|
+
variable_environment(Variables, Environment),
|
|
372
|
+
materialize_head(Head, Environment, MaterialHead),
|
|
373
|
+
materialize_expression(Body, Environment, MaterialBody),
|
|
374
|
+
maplist(materialize_expression_with(Environment), Exceptions, MaterialExceptions).
|
|
375
|
+
|
|
376
|
+
variable_environment([], []).
|
|
377
|
+
variable_environment([variable(Name, _Type, _Quantifier)|Rest], [Name-_Variable|Environment]) :-
|
|
378
|
+
variable_environment(Rest, Environment).
|
|
379
|
+
|
|
380
|
+
materialize_head(none, _Variables, none).
|
|
381
|
+
materialize_head(Head, Variables, MaterialHead) :- materialize_atom(Head, Variables, MaterialHead).
|
|
382
|
+
|
|
383
|
+
materialize_expression(none, _Variables, none).
|
|
384
|
+
materialize_expression(Expression, Variables, Materialized) :-
|
|
385
|
+
Expression = atom(_, _, _, _, _), !,
|
|
386
|
+
materialize_atom(Expression, Variables, Materialized).
|
|
387
|
+
materialize_expression(all(Items), Variables, all(MaterialItems)) :- maplist(materialize_expression_with(Variables), Items, MaterialItems).
|
|
388
|
+
materialize_expression(any(Items), Variables, any(MaterialItems)) :- maplist(materialize_expression_with(Variables), Items, MaterialItems).
|
|
389
|
+
materialize_expression(not(Atom), Variables, not(MaterialAtom)) :- materialize_atom(Atom, Variables, MaterialAtom).
|
|
390
|
+
materialize_expression(compare(Op, Left, Right), Variables, compare(Op, MaterialLeft, MaterialRight)) :-
|
|
391
|
+
materialize_term(Left, Variables, MaterialLeft), materialize_term(Right, Variables, MaterialRight).
|
|
392
|
+
materialize_expression(count(Atom, Op, Value), Variables, count(MaterialAtom, Op, Value)) :- materialize_atom(Atom, Variables, MaterialAtom).
|
|
393
|
+
materialize_expression(temporal(Relation, Left, Right), Variables, temporal(Relation, MaterialLeft, MaterialRight)) :-
|
|
394
|
+
materialize_term(Left, Variables, MaterialLeft), materialize_term(Right, Variables, MaterialRight).
|
|
395
|
+
materialize_expression_with(Variables, Expression, Materialized) :- materialize_expression(Expression, Variables, Materialized).
|
|
396
|
+
|
|
397
|
+
materialize_atom(atom(Namespace, Name, Args, Polarity, ClosedWorld), Variables, atom(Namespace, Name, MaterialArgs, Polarity, ClosedWorld)) :-
|
|
398
|
+
maplist(materialize_term_with(Variables), Args, MaterialArgs).
|
|
399
|
+
materialize_term(var(Name, _Type), Variables, Variable) :- memberchk(Name-Variable, Variables), !.
|
|
400
|
+
materialize_term(const(Value, Type), _Variables, const(Value, Type)).
|
|
401
|
+
materialize_term(number(Value, Unit), _Variables, number(Value, Unit)).
|
|
402
|
+
materialize_term(duration(Value, Unit), _Variables, duration(Value, Unit)).
|
|
403
|
+
materialize_term(timestamp(Value), _Variables, timestamp(Value)).
|
|
404
|
+
materialize_term(interval(Start, End), _Variables, interval(Start, End)).
|
|
405
|
+
materialize_term_with(Variables, Term, Materialized) :- materialize_term(Term, Variables, Materialized).
|
|
406
|
+
|
|
407
|
+
prove_expression(Atom, Seen, Depth, Proof) :-
|
|
408
|
+
Atom = atom(_, _, _, _, _),
|
|
409
|
+
derive_goal(Atom, Seen, Depth, Proof).
|
|
410
|
+
prove_expression(all(Items), Seen, Depth, all(Proofs)) :- maplist(prove_expression_seen(Seen, Depth), Items, Proofs).
|
|
411
|
+
prove_expression(any(Items), Seen, Depth, Proof) :- member(Item, Items), prove_expression(Item, Seen, Depth, Proof), !.
|
|
412
|
+
prove_expression(not(Atom), Seen, Depth, negated(Atom)) :-
|
|
413
|
+
Atom = atom(Namespace, Name, Args, Polarity, _),
|
|
414
|
+
\+ derive_goal(atom(Namespace, Name, Args, Polarity, _), Seen, Depth, _).
|
|
415
|
+
prove_expression(compare(Op, Left, Right), _Seen, _Depth, comparison(Op, Left, Right)) :- compare_terms(Op, Left, Right).
|
|
416
|
+
prove_expression(count(Atom, Op, Value), _Seen, _Depth, count(Atom, Op, Value)) :-
|
|
417
|
+
Atom = atom(Namespace, Name, Args, Polarity, _),
|
|
418
|
+
aggregate_all(count, predicate_fact(_, Namespace, Name, Args, Polarity), Count),
|
|
419
|
+
compare_numbers(Op, Count, Value).
|
|
420
|
+
prove_expression(temporal(Relation, Left, Right), _Seen, _Depth, temporal(Relation, Left, Right)) :- temporal_holds(Relation, Left, Right).
|
|
421
|
+
prove_expression_seen(Seen, Depth, Item, Proof) :- prove_expression(Item, Seen, Depth, Proof).
|
|
422
|
+
|
|
423
|
+
exceptions_do_not_hold([], _Seen, _Depth).
|
|
424
|
+
exceptions_do_not_hold([Exception|Rest], Seen, Depth) :-
|
|
425
|
+
\+ prove_expression(Exception, Seen, Depth, _),
|
|
426
|
+
exceptions_do_not_hold(Rest, Seen, Depth).
|
|
427
|
+
|
|
428
|
+
atom_signature_for_seen(atom(Namespace, Name, Args, Polarity, _), signature(Namespace, Name, Args, Polarity)).
|
|
429
|
+
|
|
430
|
+
compare_terms(eq, Left, Right) :- term_value(Left, L), term_value(Right, R), L = R.
|
|
431
|
+
compare_terms(neq, Left, Right) :- term_value(Left, L), term_value(Right, R), L \= R.
|
|
432
|
+
compare_terms(lt, Left, Right) :- term_value(Left, L), term_value(Right, R), L < R.
|
|
433
|
+
compare_terms(lte, Left, Right) :- term_value(Left, L), term_value(Right, R), L =< R.
|
|
434
|
+
compare_terms(gt, Left, Right) :- term_value(Left, L), term_value(Right, R), L > R.
|
|
435
|
+
compare_terms(gte, Left, Right) :- term_value(Left, L), term_value(Right, R), L >= R.
|
|
436
|
+
|
|
437
|
+
term_value(number(Value, Unit), Normalized) :- normalize_numeric_unit(Value, Unit, Normalized).
|
|
438
|
+
term_value(duration(Value, Unit), Normalized) :- duration_milliseconds(Value, Unit, Normalized).
|
|
439
|
+
term_value(const(Value, _), Number) :- atom_number(Value, Number), !.
|
|
440
|
+
term_value(const(Value, _), Value).
|
|
441
|
+
term_value(Value, Value) :- number(Value).
|
|
442
|
+
|
|
443
|
+
normalize_numeric_unit(Value, none, Value) :- !.
|
|
444
|
+
normalize_numeric_unit(Value, Unit, Normalized) :-
|
|
445
|
+
duration_milliseconds(Value, Unit, Normalized).
|
|
446
|
+
|
|
447
|
+
duration_milliseconds(Value, ms, Value) :- !.
|
|
448
|
+
duration_milliseconds(Value, s, Normalized) :- Normalized is Value * 1000, !.
|
|
449
|
+
duration_milliseconds(Value, m, Normalized) :- Normalized is Value * 60000, !.
|
|
450
|
+
duration_milliseconds(Value, h, Normalized) :- Normalized is Value * 3600000, !.
|
|
451
|
+
duration_milliseconds(Value, d, Normalized) :- Normalized is Value * 86400000, !.
|
|
452
|
+
duration_milliseconds(Value, w, Normalized) :- Normalized is Value * 604800000, !.
|
|
453
|
+
duration_milliseconds(Value, _Unit, Value).
|
|
454
|
+
|
|
455
|
+
compare_numbers(eq, L, R) :- L =:= R.
|
|
456
|
+
compare_numbers(neq, L, R) :- L =\= R.
|
|
457
|
+
compare_numbers(lt, L, R) :- L < R.
|
|
458
|
+
compare_numbers(lte, L, R) :- L =< R.
|
|
459
|
+
compare_numbers(gt, L, R) :- L > R.
|
|
460
|
+
compare_numbers(gte, L, R) :- L >= R.
|
|
461
|
+
|
|
462
|
+
temporal_holds(before, Left, Right) :-
|
|
463
|
+
temporal_end(Left, LeftEnd),
|
|
464
|
+
temporal_start(Right, RightStart),
|
|
465
|
+
LeftEnd @< RightStart.
|
|
466
|
+
temporal_holds(after, Left, Right) :-
|
|
467
|
+
temporal_start(Left, LeftStart),
|
|
468
|
+
temporal_end(Right, RightEnd),
|
|
469
|
+
LeftStart @> RightEnd.
|
|
470
|
+
temporal_holds(during, timestamp(Point), interval(Start, End)) :-
|
|
471
|
+
Point @>= Start,
|
|
472
|
+
Point @=< End.
|
|
473
|
+
temporal_holds(during, interval(InnerStart, InnerEnd), interval(Start, End)) :-
|
|
474
|
+
InnerStart @>= Start,
|
|
475
|
+
InnerEnd @=< End.
|
|
476
|
+
temporal_holds(overlaps, interval(LeftStart, LeftEnd), interval(RightStart, RightEnd)) :-
|
|
477
|
+
LeftStart @< RightEnd,
|
|
478
|
+
RightStart @< LeftEnd.
|
|
479
|
+
temporal_holds(starts, interval(LeftStart, _), interval(RightStart, _)) :-
|
|
480
|
+
LeftStart == RightStart.
|
|
481
|
+
temporal_holds(finishes, interval(_, LeftEnd), interval(_, RightEnd)) :-
|
|
482
|
+
LeftEnd == RightEnd.
|
|
483
|
+
|
|
484
|
+
temporal_start(timestamp(Value), Value).
|
|
485
|
+
temporal_start(interval(Start, _), Start).
|
|
486
|
+
temporal_end(timestamp(Value), Value).
|
|
487
|
+
temporal_end(interval(_, End), End).
|
|
488
|
+
|
|
489
|
+
%% logic_rule_conflict(+RuleA, +RuleB, -Status)
|
|
490
|
+
% Conservative symbolic conflict result. Identical opposing heads are a
|
|
491
|
+
% proven conflict; different bodies remain unresolved unless their explicit
|
|
492
|
+
% numeric/temporal constraints are disjoint.
|
|
493
|
+
logic_rule_conflict(RuleA, RuleB, contradiction) :-
|
|
494
|
+
opposing_rule_heads(RuleA, RuleB),
|
|
495
|
+
compatible_rule_context(RuleA, RuleB),
|
|
496
|
+
same_rule_body(RuleA, RuleB), !.
|
|
497
|
+
logic_rule_conflict(RuleA, RuleB, unresolved) :-
|
|
498
|
+
opposing_rule_heads(RuleA, RuleB),
|
|
499
|
+
compatible_rule_context(RuleA, RuleB), !.
|
|
500
|
+
logic_rule_conflict(_, _, disjoint).
|
|
501
|
+
|
|
502
|
+
%% logic_rules_stratified(+Rules)
|
|
503
|
+
% Check the finite dependency graph for a negated edge on a cycle. A single
|
|
504
|
+
% rule can be locally safe while a collection of rules still forms an
|
|
505
|
+
% unstratified negation cycle (p(X) :- not q(X), q(X) :- not p(X)). The
|
|
506
|
+
% interpreter therefore exposes this collection-level check to the KB
|
|
507
|
+
% validation layer instead of relying on Prolog's negation-as-failure.
|
|
508
|
+
logic_rules_stratified(Rules) :-
|
|
509
|
+
is_list(Rules),
|
|
510
|
+
findall(
|
|
511
|
+
edge(HeadPredicate, BodyPredicate, Sign),
|
|
512
|
+
( member(Rule, Rules), rule_dependency_edge(Rule, HeadPredicate, BodyPredicate, Sign) ),
|
|
513
|
+
Edges
|
|
514
|
+
),
|
|
515
|
+
\+ (
|
|
516
|
+
member(edge(From, To, negative), Edges),
|
|
517
|
+
dependency_path(To, From, Edges, [])
|
|
518
|
+
).
|
|
519
|
+
|
|
520
|
+
rule_dependency_edge(
|
|
521
|
+
rule(rule, _Modality, Head, Body, _Exceptions, _Scope, _From, _To, _Schema, _Variables),
|
|
522
|
+
HeadPredicate,
|
|
523
|
+
BodyPredicate,
|
|
524
|
+
Sign
|
|
525
|
+
) :-
|
|
526
|
+
logic_predicate_signature(Head, HeadPredicate),
|
|
527
|
+
expression_dependency(Body, BodyAtom, Sign),
|
|
528
|
+
logic_predicate_signature(BodyAtom, BodyPredicate).
|
|
529
|
+
|
|
530
|
+
logic_predicate_signature(atom(Namespace, Name, Args, _Polarity, _ClosedWorld), predicate(Namespace, Name, Arity)) :-
|
|
531
|
+
length(Args, Arity).
|
|
532
|
+
|
|
533
|
+
expression_dependency(atom(_Namespace, _Name, _Args, _Polarity, _ClosedWorld), Atom, positive) :-
|
|
534
|
+
Atom = atom(_, _, _, _, _).
|
|
535
|
+
expression_dependency(not(Atom), Atom, negative).
|
|
536
|
+
expression_dependency(all(Items), Atom, Sign) :- member(Item, Items), expression_dependency(Item, Atom, Sign).
|
|
537
|
+
expression_dependency(any(Items), Atom, Sign) :- member(Item, Items), expression_dependency(Item, Atom, Sign).
|
|
538
|
+
expression_dependency(count(Atom, _Operator, _Value), Atom, positive).
|
|
539
|
+
|
|
540
|
+
dependency_path(Current, Target, _Edges, Seen) :-
|
|
541
|
+
Current == Target,
|
|
542
|
+
\+ memberchk(Current, Seen),
|
|
543
|
+
!.
|
|
544
|
+
dependency_path(Current, Target, Edges, Seen) :-
|
|
545
|
+
\+ memberchk(Current, Seen),
|
|
546
|
+
member(edge(Current, Next, _Sign), Edges),
|
|
547
|
+
dependency_path(Next, Target, Edges, [Current|Seen]).
|
|
548
|
+
|
|
549
|
+
%% logic_rule_conflict_witness(+RuleA, +RuleB, -Witness)
|
|
550
|
+
% Return a structured, non-executable witness for an opposing rule result.
|
|
551
|
+
% Consumers can attach requirement IDs and source spans without ever treating
|
|
552
|
+
% the rendered Prolog preview as executable source.
|
|
553
|
+
logic_rule_conflict_witness(RuleA, RuleB, Witness) :-
|
|
554
|
+
logic_rule_conflict(RuleA, RuleB, Status),
|
|
555
|
+
RuleA = rule(_KindA, ModalityA, HeadA, BodyA, ExceptionsA, ScopeA, FromA, ToA, SchemaA, _VariablesA),
|
|
556
|
+
RuleB = rule(_KindB, ModalityB, HeadB, BodyB, ExceptionsB, ScopeB, FromB, ToB, SchemaB, _VariablesB),
|
|
557
|
+
Witness = witness{
|
|
558
|
+
status: Status,
|
|
559
|
+
modality_a: ModalityA,
|
|
560
|
+
modality_b: ModalityB,
|
|
561
|
+
head_a: HeadA,
|
|
562
|
+
head_b: HeadB,
|
|
563
|
+
body_a: BodyA,
|
|
564
|
+
body_b: BodyB,
|
|
565
|
+
exceptions_a: ExceptionsA,
|
|
566
|
+
exceptions_b: ExceptionsB,
|
|
567
|
+
scope_a: ScopeA,
|
|
568
|
+
scope_b: ScopeB,
|
|
569
|
+
valid_from_a: FromA,
|
|
570
|
+
valid_to_a: ToA,
|
|
571
|
+
valid_from_b: FromB,
|
|
572
|
+
valid_to_b: ToB,
|
|
573
|
+
rule_schema_a: SchemaA,
|
|
574
|
+
rule_schema_b: SchemaB
|
|
575
|
+
}.
|
|
576
|
+
|
|
577
|
+
opposing_rule_heads(rule(_, ModalityA, HeadA, _, _, _, _, _, _, _), rule(_, ModalityB, HeadB, _, _, _, _, _, _, _)) :-
|
|
578
|
+
conflicting_modality(ModalityA, ModalityB),
|
|
579
|
+
copy_term((HeadA, HeadB), (CopyA, CopyB)),
|
|
580
|
+
CopyA = CopyB.
|
|
581
|
+
|
|
582
|
+
conflicting_modality(assert, deny).
|
|
583
|
+
conflicting_modality(deny, assert).
|
|
584
|
+
conflicting_modality(oblige, forbid).
|
|
585
|
+
conflicting_modality(forbid, oblige).
|
|
586
|
+
conflicting_modality(permit, forbid).
|
|
587
|
+
conflicting_modality(forbid, permit).
|
|
588
|
+
|
|
589
|
+
compatible_rule_context(rule(_, _, _, BodyA, _, scope(AuthA, ScopeA, _), FromA, ToA, _, _), rule(_, _, _, BodyB, _, scope(AuthB, ScopeB, _), FromB, ToB, _, _)) :-
|
|
590
|
+
(AuthA == '' ; AuthB == '' ; AuthA == AuthB),
|
|
591
|
+
(ScopeA == '' ; ScopeB == '' ; ScopeA == ScopeB),
|
|
592
|
+
(FromA == '' ; ToB == '' ; FromA @=< ToB),
|
|
593
|
+
(FromB == '' ; ToA == '' ; FromB @=< ToA),
|
|
594
|
+
bodies_may_overlap(BodyA, BodyB).
|
|
595
|
+
|
|
596
|
+
bodies_may_overlap(none, _).
|
|
597
|
+
bodies_may_overlap(_, none).
|
|
598
|
+
bodies_may_overlap(BodyA, BodyB) :-
|
|
599
|
+
BodyA \= none,
|
|
600
|
+
BodyB \= none,
|
|
601
|
+
\+ bodies_definitely_disjoint(BodyA, BodyB).
|
|
602
|
+
|
|
603
|
+
bodies_definitely_disjoint(BodyA, _BodyB) :- expression_definitely_false(BodyA), !.
|
|
604
|
+
bodies_definitely_disjoint(_BodyA, BodyB) :- expression_definitely_false(BodyB), !.
|
|
605
|
+
|
|
606
|
+
% Only classify a body as impossible when its finite, ground comparison or
|
|
607
|
+
% temporal constraint can be evaluated to false. Unknown atoms, variables,
|
|
608
|
+
% and unsupported combinations remain unresolved rather than being treated as
|
|
609
|
+
% disjoint by guesswork.
|
|
610
|
+
expression_definitely_false(compare(Op, Left, Right)) :-
|
|
611
|
+
ground(compare(Op, Left, Right)),
|
|
612
|
+
\+ catch(compare_terms(Op, Left, Right), _, fail).
|
|
613
|
+
expression_definitely_false(temporal(Relation, Left, Right)) :-
|
|
614
|
+
ground(temporal(Relation, Left, Right)),
|
|
615
|
+
\+ catch(temporal_holds(Relation, Left, Right), _, fail).
|
|
616
|
+
expression_definitely_false(all(Items)) :-
|
|
617
|
+
member(Item, Items),
|
|
618
|
+
expression_definitely_false(Item), !.
|
|
619
|
+
expression_definitely_false(any(Items)) :-
|
|
620
|
+
Items \= [],
|
|
621
|
+
maplist(expression_definitely_false, Items).
|
|
622
|
+
|
|
623
|
+
same_rule_body(RuleA, RuleB) :-
|
|
624
|
+
RuleA = rule(_, _, _, BodyA, ExceptionsA, ScopeA, FromA, ToA, _, _),
|
|
625
|
+
RuleB = rule(_, _, _, BodyB, ExceptionsB, ScopeB, FromB, ToB, _, _),
|
|
626
|
+
BodyA == BodyB, ExceptionsA == ExceptionsB, ScopeA == ScopeB, FromA == FromB, ToA == ToB.
|
|
627
|
+
|
|
628
|
+
normalize_atom(Value, Atom) :- atom(Value), !, Atom = Value.
|
|
629
|
+
normalize_atom(Value, Atom) :- string(Value), atom_string(Atom, Value).
|