kibi-core 0.24.0 → 0.26.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/validation.pl +3 -1
- package/src/certificate_check.pl +38 -4
- package/src/certificates.pl +10 -2
- package/src/claim_obligations.pl +356 -28
- package/src/code_certificates.pl +239 -4
- package/src/kb.pl +49 -2
- package/src/model_check.pl +728 -0
- package/src/requirement_proof.pl +10 -3
- package/src/rule_registry.pl +1 -1
package/package.json
CHANGED
package/schema/validation.pl
CHANGED
|
@@ -157,6 +157,8 @@ validate_fact_shape(property_value, Props) :-
|
|
|
157
157
|
valid_operator(Op),
|
|
158
158
|
memberchk(value_type=VT, Props),
|
|
159
159
|
valid_value_type(VT),
|
|
160
|
+
( Op == matches -> VT == string ; true ),
|
|
161
|
+
( memberchk(Op, [includes, subset_of, excludes]) -> VT == set ; true ),
|
|
160
162
|
exactly_one_value_field(Props),
|
|
161
163
|
value_type_matches_field(VT, Props),
|
|
162
164
|
valid_optional_fact_enums(Props),
|
|
@@ -225,7 +227,7 @@ text_atom(Value, Atom) :- atom(Value), !, Atom = Value.
|
|
|
225
227
|
text_atom(Value, Atom) :- string(Value), atom_string(Atom, Value).
|
|
226
228
|
|
|
227
229
|
% valid_operator(+Op)
|
|
228
|
-
valid_operator(Op) :- memberchk(Op, [eq, neq, lt, lte, gt, gte]), !.
|
|
230
|
+
valid_operator(Op) :- memberchk(Op, [eq, neq, lt, lte, gt, gte, matches, includes, subset_of, excludes]), !.
|
|
229
231
|
|
|
230
232
|
% valid_value_type(+VT)
|
|
231
233
|
valid_value_type(ValueType) :- memberchk(ValueType, [string, int, number, bool, set]), !.
|
package/src/certificate_check.pl
CHANGED
|
@@ -204,7 +204,7 @@ context_domain(Members, Domain, Reason) :-
|
|
|
204
204
|
; Types == [string]
|
|
205
205
|
-> Domain = string, context_scalar_reason(Members, Reason)
|
|
206
206
|
; Types == [set]
|
|
207
|
-
-> Domain = set,
|
|
207
|
+
-> Domain = set, context_set_reason(Members, Reason)
|
|
208
208
|
; Types = [_, _|_]
|
|
209
209
|
-> Domain = none, Reason = "the facts use different value types"
|
|
210
210
|
; Domain = none, Reason = "the value type is not supported"
|
|
@@ -228,6 +228,25 @@ context_scalar_reason(Members, Reason) :-
|
|
|
228
228
|
; Reason = none
|
|
229
229
|
).
|
|
230
230
|
|
|
231
|
+
% Sets also compare with includes (every member of the bound has the
|
|
232
|
+
% property), subset_of (every member with the property is in the bound) and
|
|
233
|
+
% excludes (no member of the bound has it), but only as requirements: a
|
|
234
|
+
% forbidden one has no single negation among the set operators. neq beside
|
|
235
|
+
% subset_of or excludes is left unresolved: the finite candidates below decide
|
|
236
|
+
% those only without neq.
|
|
237
|
+
context_set_reason(Members, Reason) :-
|
|
238
|
+
( member(_-tuple(_, _, Op, _, _, _, _, Polarity, _, _), Members),
|
|
239
|
+
( \+ memberchk(Op, [eq, neq, includes, subset_of, excludes])
|
|
240
|
+
; memberchk(Op, [includes, subset_of, excludes]), Polarity == forbid
|
|
241
|
+
)
|
|
242
|
+
-> Reason = "only eq, neq and a required includes, subset_of or excludes compare set values"
|
|
243
|
+
; member(_-tuple(_, _, neq, _, _, _, _, _, _, _), Members),
|
|
244
|
+
member(_-tuple(_, _, Bounding, _, _, _, _, _, _, _), Members),
|
|
245
|
+
memberchk(Bounding, [subset_of, excludes])
|
|
246
|
+
-> Reason = "neq beside subset_of or excludes on one set property is not compared"
|
|
247
|
+
; Reason = none
|
|
248
|
+
).
|
|
249
|
+
|
|
231
250
|
context_member(_Domain, Fact-tuple(_, _, Op0, _, Value, _, _, Polarity, From, To), m(Fact, Op, Value, From, To)) :-
|
|
232
251
|
( Polarity == forbid
|
|
233
252
|
-> numeric_constraint_negation(c(Op0, _, Value), c(Op, _, _))
|
|
@@ -250,13 +269,17 @@ certificate_member_holds(Domain, m(_, Op, Bound, _, _), Value) :-
|
|
|
250
269
|
; Op == neq -> ValueText \== BoundText
|
|
251
270
|
).
|
|
252
271
|
|
|
253
|
-
% A set value is the exact set of
|
|
254
|
-
% members in any order, neq for any other set
|
|
272
|
+
% A set value is the exact set of members with the property: eq holds for
|
|
273
|
+
% the same members in any order, neq for any other set, includes for any set
|
|
274
|
+
% that contains every member of the bound.
|
|
255
275
|
certificate_member_holds(set, m(_, Op, Bound, _, _), Value) :-
|
|
256
276
|
set_texts(Value, ValueTexts),
|
|
257
277
|
set_texts(Bound, BoundTexts),
|
|
258
278
|
( Op == eq -> ValueTexts == BoundTexts
|
|
259
279
|
; Op == neq -> ValueTexts \== BoundTexts
|
|
280
|
+
; Op == includes -> subtract(BoundTexts, ValueTexts, [])
|
|
281
|
+
; Op == subset_of -> subtract(ValueTexts, BoundTexts, [])
|
|
282
|
+
; Op == excludes -> \+ ( member(Text, ValueTexts), memberchk(Text, BoundTexts) )
|
|
260
283
|
).
|
|
261
284
|
|
|
262
285
|
%% set_texts(+Value, -Texts)
|
|
@@ -308,7 +331,18 @@ scalar_candidates(set, Members, Candidates) :-
|
|
|
308
331
|
sort(Texts0, Texts),
|
|
309
332
|
atomic_list_concat(["kibi:fresh"|Texts], '|', FreshAtom),
|
|
310
333
|
atom_string(FreshAtom, Fresh),
|
|
311
|
-
|
|
334
|
+
% The union of every bound satisfies any set of includes facts alone; the
|
|
335
|
+
% union of the includes bounds is the least set every includes fact
|
|
336
|
+
% allows, so with subset_of and excludes (and no eq or neq) it is
|
|
337
|
+
% satisfiable exactly when that union is.
|
|
338
|
+
includes_union(Members, Included),
|
|
339
|
+
append(Sets, [Texts, Included, [Fresh]], Candidates).
|
|
340
|
+
|
|
341
|
+
includes_union(Members, Union) :-
|
|
342
|
+
findall(Text,
|
|
343
|
+
(member(m(_, includes, Bound, _, _), Members), set_texts(Bound, Set), member(Text, Set)),
|
|
344
|
+
Texts),
|
|
345
|
+
sort(Texts, Union).
|
|
312
346
|
|
|
313
347
|
property_theorem(Contexts, Numbered, Theorem, Used, Rejected) :-
|
|
314
348
|
findall(Index-Certificate,
|
package/src/certificates.pl
CHANGED
|
@@ -125,9 +125,17 @@ model_candidates(string, Members, Candidates) :-
|
|
|
125
125
|
model_candidates(set, Members, Candidates) :-
|
|
126
126
|
findall(Set, (member(m(_, _, Bound, _, _), Members), set_texts(Bound, Set)), Sets0),
|
|
127
127
|
sort(Sets0, Sets),
|
|
128
|
-
findall(Text, (member(Set, Sets), member(Text, Set)),
|
|
128
|
+
findall(Text, (member(Set, Sets), member(Text, Set)), Texts0),
|
|
129
|
+
sort(Texts0, Texts),
|
|
129
130
|
fresh_text(Texts, 0, Fresh),
|
|
130
|
-
|
|
131
|
+
% The union of every bound satisfies any set of includes facts alone;
|
|
132
|
+
% the union of the includes bounds alone is the least set they allow,
|
|
133
|
+
% which subset_of and excludes facts accept when anything does.
|
|
134
|
+
findall(Text,
|
|
135
|
+
(member(m(_, includes, Bound, _, _), Members), set_texts(Bound, Set), member(Text, Set)),
|
|
136
|
+
Included0),
|
|
137
|
+
sort(Included0, Included),
|
|
138
|
+
append(Sets, [Texts, Included, [Fresh]], Candidates).
|
|
131
139
|
|
|
132
140
|
fresh_text(Bounds, Index, Fresh) :-
|
|
133
141
|
format(string(Candidate), "kibi:model:~w", [Index]),
|
package/src/claim_obligations.pl
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
% Module: claim_obligations
|
|
2
|
-
% implements REQ-kibi-claim-bound-proofs
|
|
2
|
+
% implements REQ-kibi-claim-bound-proofs, REQ-kibi-non-numeric-proof-kinds
|
|
3
3
|
%
|
|
4
4
|
% What a certificate has to establish for it to prove one requirement claim.
|
|
5
5
|
% A claim is grounded by exactly one typed fact; the obligation is derived
|
|
@@ -8,16 +8,47 @@
|
|
|
8
8
|
%
|
|
9
9
|
% set fact (value_type set, or the legacy allowed_values string "a|b|c")
|
|
10
10
|
% exhaustive, typecheck the domain is exactly the fact's set
|
|
11
|
+
% typecheck with property required_fields: every member
|
|
12
|
+
% is a required, non-nullable field of the type
|
|
13
|
+
% rule every member satisfies the predicate the
|
|
14
|
+
% fact's property names
|
|
15
|
+
% state_machine with property transitions, states,
|
|
16
|
+
% initial_states, terminal_states or
|
|
17
|
+
% reachable_states: that projection of the
|
|
18
|
+
% machine is exactly the set
|
|
11
19
|
% numeric fact (value_type int or number, a comparison operator)
|
|
12
20
|
% arithmetic, smt the certificate's claim is the fact's constraint
|
|
13
21
|
% (negated for polarity forbid)
|
|
22
|
+
% rule fact (kibi.logic.v1)
|
|
23
|
+
% rule, state_machine the rule holds for every assignment of its
|
|
24
|
+
% variables over their domains (model_check.pl)
|
|
25
|
+
% predicate fact (a ground atom asserted or denied)
|
|
26
|
+
% rule, state_machine the code decides the atom as the fact says
|
|
27
|
+
% string fact (value_type string, operator eq, neq or matches)
|
|
28
|
+
% regex the code's string or pattern meets the
|
|
29
|
+
% constraint (decided in the CLI, exactly)
|
|
30
|
+
% set fact under source (any operator: eq, includes, subset_of, excludes)
|
|
31
|
+
% source the members Kibi read from the project source
|
|
32
|
+
% stand in the operator's relation to the set;
|
|
33
|
+
% with property required_fields or
|
|
34
|
+
% nullable_fields every selected table or object
|
|
35
|
+
% has each member as a required (or nullable)
|
|
36
|
+
% field; with property middleware every
|
|
37
|
+
% selected route's middleware stands in the
|
|
38
|
+
% relation
|
|
39
|
+
% set fact under state_machine with operator includes, subset_of or
|
|
40
|
+
% excludes, or with property reachable_pairs or required_waypoints
|
|
41
|
+
% state_machine the projection stands in the operator's
|
|
42
|
+
% relation to the set; a required waypoint
|
|
43
|
+
% "from->to via mid" holds when every path from
|
|
44
|
+
% one state to the other passes through mid
|
|
14
45
|
%
|
|
15
|
-
% Any other pairing
|
|
16
|
-
% incompatible (a set fact under arithmetic, a numeric fact under
|
|
17
|
-
% exhaustive). A fact kind with no derivation
|
|
18
|
-
%
|
|
19
|
-
% says the certificate expresses the claim, and Kibi never counts that
|
|
20
|
-
% proved.
|
|
46
|
+
% Any other pairing of a set, numeric or string fact with a certificate kind
|
|
47
|
+
% is incompatible (a set fact under arithmetic, a numeric fact under
|
|
48
|
+
% exhaustive). A fact kind with no derivation for the proof kind (boolean
|
|
49
|
+
% placeholders, a rule under exhaustive) can only be attested: the test
|
|
50
|
+
% author says the certificate expresses the claim, and Kibi never counts that
|
|
51
|
+
% as proved.
|
|
21
52
|
|
|
22
53
|
:- module(claim_obligations, [
|
|
23
54
|
claim_obligation/3,
|
|
@@ -25,38 +56,237 @@
|
|
|
25
56
|
obligation_equal/2,
|
|
26
57
|
proof_contract_claim_candidates_json/1,
|
|
27
58
|
proof_contract_claim_issues_json/1,
|
|
28
|
-
test_claim_obligations_json/4
|
|
59
|
+
test_claim_obligations_json/4,
|
|
60
|
+
claim_model_check_json/4
|
|
29
61
|
]).
|
|
30
62
|
|
|
31
63
|
:- use_module(library(http/json)).
|
|
32
64
|
:- use_module(library(apply)).
|
|
33
65
|
:- use_module(library(lists)).
|
|
34
66
|
:- use_module('kb.pl').
|
|
67
|
+
:- use_module('model_check.pl', [model_check_obligation/4, edge_text/3]).
|
|
35
68
|
|
|
36
69
|
%% fact_obligation(+FactId, +ProofKind, -Result)
|
|
37
70
|
% Result is obligation(Dict), attested, or incompatible(Reason).
|
|
38
71
|
fact_obligation(FactId, ProofKind, Result) :-
|
|
39
|
-
( fact_set(FactId, Members)
|
|
40
|
-
-> (
|
|
41
|
-
->
|
|
72
|
+
( fact_set(FactId, Members, Operator)
|
|
73
|
+
-> ( ProofKind == source
|
|
74
|
+
-> source_obligation(FactId, Members, Operator, Result)
|
|
75
|
+
; ProofKind == state_machine
|
|
76
|
+
-> machine_obligation(FactId, Members, Operator, Result)
|
|
77
|
+
; Operator == includes
|
|
78
|
+
-> includes_obligation(FactId, Members, ProofKind, Result)
|
|
79
|
+
; Operator == eq
|
|
80
|
+
-> set_obligation(FactId, Members, ProofKind, Result)
|
|
42
81
|
; format(string(Reason),
|
|
43
|
-
"~w is a set fact, which
|
|
44
|
-
[FactId, ProofKind]),
|
|
82
|
+
"~w is a ~w set fact, which only source and state_machine certificates can derive (a ~w certificate compares the whole set)",
|
|
83
|
+
[FactId, Operator, ProofKind]),
|
|
45
84
|
Result = incompatible(Reason)
|
|
46
85
|
)
|
|
47
86
|
; fact_numeric_constraint(FactId, Variable, ValueType, Operator, Value)
|
|
48
87
|
-> ( memberchk(ProofKind, [arithmetic, smt])
|
|
49
88
|
-> Result = obligation(_{kind: "numeric_constraint", variable: Variable,
|
|
50
89
|
value_type: ValueType, operator: Operator, value: Value})
|
|
51
|
-
; ( ProofKind
|
|
90
|
+
; ( memberchk(ProofKind, [typecheck, source]) -> Article = a ; Article = an ),
|
|
52
91
|
format(string(Reason),
|
|
53
92
|
"~w is a numeric fact, which ~w ~w certificate cannot express (use arithmetic or smt)",
|
|
54
93
|
[FactId, Article, ProofKind]),
|
|
55
94
|
Result = incompatible(Reason)
|
|
56
95
|
)
|
|
96
|
+
; memberchk(ProofKind, [rule, state_machine]),
|
|
97
|
+
fact_rule_hash(FactId, Hash)
|
|
98
|
+
-> atom_string(FactId, FactString),
|
|
99
|
+
Result = obligation(_{kind: "rule_holds", fact_id: FactString, rule_hash: Hash})
|
|
100
|
+
; memberchk(ProofKind, [rule, state_machine]),
|
|
101
|
+
predicate_expectation(FactId, Name, Args, Value)
|
|
102
|
+
-> atom_string(Name, NameString),
|
|
103
|
+
maplist(atom_string, Args, ArgStrings),
|
|
104
|
+
Result = obligation(_{kind: "predicate_holds", predicate: NameString,
|
|
105
|
+
args: ArgStrings, value: Value})
|
|
106
|
+
; fact_string_constraint(FactId, Operator, Value, Polarity)
|
|
107
|
+
-> ( ProofKind == regex
|
|
108
|
+
-> Result = obligation(_{kind: "string_constraint", operator: Operator,
|
|
109
|
+
value: Value, polarity: Polarity})
|
|
110
|
+
; memberchk(ProofKind, [exhaustive, typecheck, arithmetic, smt])
|
|
111
|
+
-> Result = attested
|
|
112
|
+
; format(string(Reason),
|
|
113
|
+
"~w is a string fact, which a ~w certificate cannot express (use regex)",
|
|
114
|
+
[FactId, ProofKind]),
|
|
115
|
+
Result = incompatible(Reason)
|
|
116
|
+
)
|
|
57
117
|
; Result = attested
|
|
58
118
|
).
|
|
59
119
|
|
|
120
|
+
% Set facts: what each kind derives from the members, by the property the
|
|
121
|
+
% fact names where the kind reads one.
|
|
122
|
+
set_obligation(_FactId, Members, exhaustive, obligation(_{kind: "set_equals", members: Members})) :- !.
|
|
123
|
+
set_obligation(FactId, Members, typecheck, obligation(Obligation)) :- !,
|
|
124
|
+
( fact_property_key(FactId, required_fields)
|
|
125
|
+
-> Obligation = _{kind: "required_fields", members: Members}
|
|
126
|
+
; Obligation = _{kind: "set_equals", members: Members}
|
|
127
|
+
).
|
|
128
|
+
set_obligation(FactId, Members, rule, obligation(_{kind: "every_member", predicate: Predicate, members: Members})) :-
|
|
129
|
+
fact_property_key(FactId, Property),
|
|
130
|
+
atom_string(Property, Predicate),
|
|
131
|
+
!.
|
|
132
|
+
set_obligation(FactId, Members, state_machine, Result) :- !,
|
|
133
|
+
machine_obligation(FactId, Members, eq, Result).
|
|
134
|
+
set_obligation(FactId, _Members, rule, incompatible(Reason)) :- !,
|
|
135
|
+
format(string(Reason),
|
|
136
|
+
"~w is a set fact with no property to read as the predicate every member satisfies",
|
|
137
|
+
[FactId]).
|
|
138
|
+
set_obligation(FactId, _Members, ProofKind, incompatible(Reason)) :-
|
|
139
|
+
( ProofKind == regex -> Article = a ; Article = an ),
|
|
140
|
+
format(string(Reason),
|
|
141
|
+
"~w is a set fact, which ~w ~w certificate cannot express (use exhaustive, typecheck, rule or state_machine)",
|
|
142
|
+
[FactId, Article, ProofKind]).
|
|
143
|
+
|
|
144
|
+
% includes set facts ("every member of A has P") state containment only, so
|
|
145
|
+
% only the kinds that check each member on its own can derive them: a rule
|
|
146
|
+
% checks the property on every member, and a typecheck checks each field is
|
|
147
|
+
% required. Kinds that compare the whole set would read containment as
|
|
148
|
+
% equality.
|
|
149
|
+
includes_obligation(FactId, Members, rule, Result) :- !,
|
|
150
|
+
set_obligation(FactId, Members, rule, Result).
|
|
151
|
+
includes_obligation(FactId, Members, typecheck, obligation(_{kind: "required_fields", members: Members})) :-
|
|
152
|
+
fact_property_key(FactId, required_fields),
|
|
153
|
+
!.
|
|
154
|
+
includes_obligation(FactId, _Members, ProofKind, incompatible(Reason)) :-
|
|
155
|
+
format(string(Reason),
|
|
156
|
+
"~w is an includes set fact (every member has the property), which a ~w certificate cannot derive: it compares the whole set (use rule, or an eq set fact for exactly these members)",
|
|
157
|
+
[FactId, ProofKind]).
|
|
158
|
+
|
|
159
|
+
% implements REQ-kibi-source-proof-kinds
|
|
160
|
+
% A state machine set fact: the projection its property names stands in the
|
|
161
|
+
% fact operator's relation to the members. eq on a plain projection keeps
|
|
162
|
+
% the machine_set obligation earlier receipts recorded.
|
|
163
|
+
machine_obligation(FactId, Members, Operator, Result) :-
|
|
164
|
+
( fact_property_key(FactId, Projection),
|
|
165
|
+
memberchk(Projection, [transitions, states, initial_states, terminal_states,
|
|
166
|
+
reachable_states, reachable_pairs, required_waypoints])
|
|
167
|
+
-> ( memberchk(Projection, [transitions, reachable_pairs])
|
|
168
|
+
-> maplist(edge_member, Members, Edges0),
|
|
169
|
+
sort(Edges0, Spelled)
|
|
170
|
+
; Projection == required_waypoints
|
|
171
|
+
-> maplist(waypoint_member, Members, Spelled0),
|
|
172
|
+
sort(Spelled0, Spelled)
|
|
173
|
+
; Spelled = Members
|
|
174
|
+
),
|
|
175
|
+
atom_string(Projection, ProjectionString),
|
|
176
|
+
atom_string(Operator, OperatorString),
|
|
177
|
+
( Projection == required_waypoints,
|
|
178
|
+
Operator == subset_of
|
|
179
|
+
-> format(string(Reason),
|
|
180
|
+
"~w names required waypoints with subset_of; state each waypoint with includes (they hold) or excludes (they do not)",
|
|
181
|
+
[FactId]),
|
|
182
|
+
Result = incompatible(Reason)
|
|
183
|
+
; Projection == required_waypoints,
|
|
184
|
+
\+ ( member(M, Spelled), sub_string(M, _, _, _, " via ") )
|
|
185
|
+
-> format(string(Reason),
|
|
186
|
+
"~w names required waypoints that are not spelled \"from->to via state\"",
|
|
187
|
+
[FactId]),
|
|
188
|
+
Result = incompatible(Reason)
|
|
189
|
+
; Operator == eq,
|
|
190
|
+
Projection \== required_waypoints
|
|
191
|
+
-> Result = obligation(_{kind: "machine_set", projection: ProjectionString, members: Spelled})
|
|
192
|
+
; Result = obligation(_{kind: "machine_relation", projection: ProjectionString,
|
|
193
|
+
operator: OperatorString, members: Spelled})
|
|
194
|
+
)
|
|
195
|
+
; format(string(Reason),
|
|
196
|
+
"~w is a set fact whose property is not a machine projection (transitions, states, initial_states, terminal_states, reachable_states, reachable_pairs or required_waypoints)",
|
|
197
|
+
[FactId]),
|
|
198
|
+
Result = incompatible(Reason)
|
|
199
|
+
).
|
|
200
|
+
|
|
201
|
+
% A required waypoint "from -> to via state", spelled "from->to via state".
|
|
202
|
+
waypoint_member(Member, Spelled) :-
|
|
203
|
+
( sub_string(Member, Before, _, After, " via ")
|
|
204
|
+
-> sub_string(Member, 0, Before, _, Edge0),
|
|
205
|
+
sub_string(Member, _, After, 0, Via0),
|
|
206
|
+
edge_member(Edge0, Edge),
|
|
207
|
+
normalize_space(atom(Via), Via0),
|
|
208
|
+
format(string(Spelled), "~w via ~w", [Edge, Via])
|
|
209
|
+
; Spelled = Member
|
|
210
|
+
).
|
|
211
|
+
|
|
212
|
+
% implements REQ-kibi-source-proof-kinds
|
|
213
|
+
% A set fact under the source kind. The property decides what the members
|
|
214
|
+
% are about: fields every selected table or object has as required or
|
|
215
|
+
% nullable, middleware every selected route runs, or (any other property)
|
|
216
|
+
% the members of the source itself.
|
|
217
|
+
% A required_fields or nullable_fields fact says which fields have the
|
|
218
|
+
% property; with subset_of or excludes it would be decided against whatever
|
|
219
|
+
% members the contract selects, so it is refused instead.
|
|
220
|
+
source_obligation(FactId, Members, Operator, Result) :-
|
|
221
|
+
atom_string(Operator, OperatorString),
|
|
222
|
+
( fact_property_key(FactId, Property) -> true ; Property = none ),
|
|
223
|
+
( memberchk(Property-Kind, [required_fields-"required_fields", nullable_fields-"nullable_fields"])
|
|
224
|
+
-> ( memberchk(Operator, [eq, includes])
|
|
225
|
+
-> Result = obligation(_{kind: Kind, members: Members})
|
|
226
|
+
; format(string(Reason),
|
|
227
|
+
"~w is a ~w fact with operator ~w; a source proof decides ~w only with eq or includes (to bound the fields, use a columns or fields fact)",
|
|
228
|
+
[FactId, Property, Operator, Property]),
|
|
229
|
+
Result = incompatible(Reason)
|
|
230
|
+
)
|
|
231
|
+
; Property == middleware
|
|
232
|
+
-> Result = obligation(_{kind: "route_middleware", operator: OperatorString, members: Members})
|
|
233
|
+
; Result = obligation(_{kind: "set_relation", operator: OperatorString, members: Members})
|
|
234
|
+
).
|
|
235
|
+
|
|
236
|
+
% A transition member "from -> to" (or with U+2192 as the arrow) spelled "from->to".
|
|
237
|
+
edge_member(Member, Spelled) :-
|
|
238
|
+
( sub_string(Member, Before, _, After, "->")
|
|
239
|
+
-> sub_string(Member, 0, Before, _, From0),
|
|
240
|
+
sub_string(Member, _, After, 0, To0)
|
|
241
|
+
; sub_string(Member, Before, _, After, "\u2192")
|
|
242
|
+
-> sub_string(Member, 0, Before, _, From0),
|
|
243
|
+
sub_string(Member, _, After, 0, To0)
|
|
244
|
+
; From0 = Member, To0 = ""
|
|
245
|
+
),
|
|
246
|
+
normalize_space(atom(From), From0),
|
|
247
|
+
normalize_space(atom(To), To0),
|
|
248
|
+
edge_text(From, To, Spelled).
|
|
249
|
+
|
|
250
|
+
% What a predicate fact says the code decides. A decision predicate (the
|
|
251
|
+
% built-in permission_rule, or a schema with key_arguments and a decision
|
|
252
|
+
% argument) is about the key arguments: the code's function takes them and
|
|
253
|
+
% returns whether the effective decision is allow. Any other predicate is
|
|
254
|
+
% true when asserted and false when denied.
|
|
255
|
+
predicate_expectation(FactId, Name, KeyArgs, Value) :-
|
|
256
|
+
kb:predicate_fact(FactId, Namespace, Name, Args, Polarity),
|
|
257
|
+
length(Args, Arity),
|
|
258
|
+
( kb:decision_signatures(Signatures),
|
|
259
|
+
memberchk(decision_signature(Namespace, Name, Arity, KeyPositions, DecisionPosition), Signatures)
|
|
260
|
+
-> findall(Arg, ( member(Position, KeyPositions), nth1(Position, Args, Arg) ), KeyArgs),
|
|
261
|
+
nth1(DecisionPosition, Args, Decision),
|
|
262
|
+
kb:effective_decision(Decision, Polarity, Effect),
|
|
263
|
+
( Effect == allow -> Value = true ; Value = false )
|
|
264
|
+
; memberchk(Polarity-Value, [assert-true, deny-false]),
|
|
265
|
+
KeyArgs = Args
|
|
266
|
+
).
|
|
267
|
+
|
|
268
|
+
fact_property_key(FactId, Property) :-
|
|
269
|
+
kb_entity(FactId, fact, Props),
|
|
270
|
+
prop_atom(Props, property_key, Property).
|
|
271
|
+
|
|
272
|
+
fact_rule_hash(FactId, Hash) :-
|
|
273
|
+
kb_entity(FactId, fact, Props),
|
|
274
|
+
prop_atom(Props, fact_kind, rule),
|
|
275
|
+
memberchk(rule_ir=_, Props),
|
|
276
|
+
( prop_atom(Props, rule_hash, HashAtom)
|
|
277
|
+
-> atom_string(HashAtom, Hash)
|
|
278
|
+
; Hash = ""
|
|
279
|
+
).
|
|
280
|
+
|
|
281
|
+
% A string property_value fact: eq, neq or matches on a string value.
|
|
282
|
+
fact_string_constraint(FactId, Operator, Value, Polarity) :-
|
|
283
|
+
kb:fact_property_tuple(FactId, _Subject, Property, Op, string, Raw, _Unit, _Scope, Polarity0),
|
|
284
|
+
Property \== allowed_values,
|
|
285
|
+
memberchk(Op, [eq, neq, matches]),
|
|
286
|
+
text_string(Raw, Value),
|
|
287
|
+
atom_string(Op, Operator),
|
|
288
|
+
atom_string(Polarity0, Polarity).
|
|
289
|
+
|
|
60
290
|
%% claim_obligation(+ReqId, +ClaimKey, +ProofKind, -Result)
|
|
61
291
|
% As fact_obligation/3 for the fact grounding ClaimKey in ReqId, with the
|
|
62
292
|
% fact id: obligation(FactId, Dict), attested(FactId), incompatible(FactId,
|
|
@@ -102,6 +332,59 @@ obligation_key(Obligation, set(Members)) :-
|
|
|
102
332
|
maplist(text_string, Raw, Strings),
|
|
103
333
|
sort(Strings, Members),
|
|
104
334
|
!.
|
|
335
|
+
obligation_key(Obligation, required_fields(Members)) :-
|
|
336
|
+
dict_text(Obligation, kind, "required_fields"),
|
|
337
|
+
obligation_members(Obligation, Members),
|
|
338
|
+
!.
|
|
339
|
+
obligation_key(Obligation, every(Predicate, Members)) :-
|
|
340
|
+
dict_text(Obligation, kind, "every_member"),
|
|
341
|
+
dict_text(Obligation, predicate, Predicate),
|
|
342
|
+
obligation_members(Obligation, Members),
|
|
343
|
+
!.
|
|
344
|
+
obligation_key(Obligation, nullable_fields(Members)) :-
|
|
345
|
+
dict_text(Obligation, kind, "nullable_fields"),
|
|
346
|
+
obligation_members(Obligation, Members),
|
|
347
|
+
!.
|
|
348
|
+
obligation_key(Obligation, relation(Operator, Members)) :-
|
|
349
|
+
dict_text(Obligation, kind, "set_relation"),
|
|
350
|
+
dict_text(Obligation, operator, Operator),
|
|
351
|
+
obligation_members(Obligation, Members),
|
|
352
|
+
!.
|
|
353
|
+
obligation_key(Obligation, middleware(Operator, Members)) :-
|
|
354
|
+
dict_text(Obligation, kind, "route_middleware"),
|
|
355
|
+
dict_text(Obligation, operator, Operator),
|
|
356
|
+
obligation_members(Obligation, Members),
|
|
357
|
+
!.
|
|
358
|
+
obligation_key(Obligation, machine(Projection, Operator, Members)) :-
|
|
359
|
+
dict_text(Obligation, kind, "machine_relation"),
|
|
360
|
+
dict_text(Obligation, projection, Projection),
|
|
361
|
+
dict_text(Obligation, operator, Operator),
|
|
362
|
+
obligation_members(Obligation, Members),
|
|
363
|
+
!.
|
|
364
|
+
obligation_key(Obligation, machine(Projection, Members)) :-
|
|
365
|
+
dict_text(Obligation, kind, "machine_set"),
|
|
366
|
+
dict_text(Obligation, projection, Projection),
|
|
367
|
+
obligation_members(Obligation, Members),
|
|
368
|
+
!.
|
|
369
|
+
obligation_key(Obligation, rule(FactId, Hash)) :-
|
|
370
|
+
dict_text(Obligation, kind, "rule_holds"),
|
|
371
|
+
dict_text(Obligation, fact_id, FactId),
|
|
372
|
+
dict_text(Obligation, rule_hash, Hash),
|
|
373
|
+
!.
|
|
374
|
+
obligation_key(Obligation, predicate(Name, Args, Value)) :-
|
|
375
|
+
dict_text(Obligation, kind, "predicate_holds"),
|
|
376
|
+
dict_text(Obligation, predicate, Name),
|
|
377
|
+
get_dict(args, Obligation, RawArgs),
|
|
378
|
+
is_list(RawArgs),
|
|
379
|
+
maplist(text_string, RawArgs, Args),
|
|
380
|
+
dict_text(Obligation, value, Value),
|
|
381
|
+
!.
|
|
382
|
+
obligation_key(Obligation, string(Operator, Value, Polarity)) :-
|
|
383
|
+
dict_text(Obligation, kind, "string_constraint"),
|
|
384
|
+
dict_text(Obligation, operator, Operator),
|
|
385
|
+
dict_text(Obligation, value, Value),
|
|
386
|
+
dict_text(Obligation, polarity, Polarity),
|
|
387
|
+
!.
|
|
105
388
|
obligation_key(Obligation, numeric(Variable, Type, Operator, Value)) :-
|
|
106
389
|
dict_text(Obligation, kind, "numeric_constraint"),
|
|
107
390
|
dict_text(Obligation, variable, Variable),
|
|
@@ -110,6 +393,12 @@ obligation_key(Obligation, numeric(Variable, Type, Operator, Value)) :-
|
|
|
110
393
|
get_dict(value, Obligation, Raw),
|
|
111
394
|
number_value(Raw, Value).
|
|
112
395
|
|
|
396
|
+
obligation_members(Obligation, Members) :-
|
|
397
|
+
get_dict(members, Obligation, Raw),
|
|
398
|
+
is_list(Raw),
|
|
399
|
+
maplist(text_string, Raw, Strings),
|
|
400
|
+
sort(Strings, Members).
|
|
401
|
+
|
|
113
402
|
dict_text(Dict, Key, String) :-
|
|
114
403
|
is_dict(Dict),
|
|
115
404
|
get_dict(Key, Dict, Raw),
|
|
@@ -126,13 +415,15 @@ text_string(Raw, String) :- atom(Raw), !, atom_string(Raw, String).
|
|
|
126
415
|
text_string(Raw, String) :- number(Raw), !, number_string(Raw, String).
|
|
127
416
|
text_string(^^(Raw, _), String) :- !, text_string(Raw, String).
|
|
128
417
|
|
|
129
|
-
% The members of a set fact, sorted and unique
|
|
130
|
-
|
|
418
|
+
% The members of a set fact, sorted and unique, and its operator: eq (the
|
|
419
|
+
% set is exactly these members), includes (it contains at least these),
|
|
420
|
+
% subset_of (it contains only these) or excludes (it contains none of them).
|
|
421
|
+
fact_set(FactId, Members, Operator) :-
|
|
131
422
|
kb_entity(FactId, fact, Props),
|
|
132
423
|
prop_atom(Props, fact_kind, property_value),
|
|
133
424
|
( prop_atom(Props, value_type, set)
|
|
134
425
|
-> memberchk(value_set=Raw, Props),
|
|
135
|
-
kb:
|
|
426
|
+
kb:normalize_literal_atom_list(Raw, Atoms)
|
|
136
427
|
; prop_atom(Props, property_key, allowed_values),
|
|
137
428
|
\+ (memberchk(value_type=RawType, Props),
|
|
138
429
|
kb:normalize_term_atom(RawType, Type),
|
|
@@ -144,7 +435,8 @@ fact_set(FactId, Members) :-
|
|
|
144
435
|
exclude(==(""), Parts, NonEmpty),
|
|
145
436
|
maplist([S, A]>>atom_string(A, S), NonEmpty, Atoms)
|
|
146
437
|
),
|
|
147
|
-
( prop_atom(Props, operator,
|
|
438
|
+
( prop_atom(Props, operator, Operator0) -> Operator = Operator0 ; Operator = eq ),
|
|
439
|
+
memberchk(Operator, [eq, includes, subset_of, excludes]),
|
|
148
440
|
\+ prop_atom(Props, polarity, forbid),
|
|
149
441
|
maplist([A, S]>>atom_string(A, S), Atoms, Strings),
|
|
150
442
|
sort(Strings, Members),
|
|
@@ -311,6 +603,10 @@ certificate_kind(exhaustive).
|
|
|
311
603
|
certificate_kind(typecheck).
|
|
312
604
|
certificate_kind(arithmetic).
|
|
313
605
|
certificate_kind(smt).
|
|
606
|
+
certificate_kind(rule).
|
|
607
|
+
certificate_kind(state_machine).
|
|
608
|
+
certificate_kind(regex).
|
|
609
|
+
certificate_kind(source).
|
|
314
610
|
|
|
315
611
|
test_certificate_contract(TestId, Kind, Contract) :-
|
|
316
612
|
kb_entity(TestId, test, Props),
|
|
@@ -352,10 +648,14 @@ proof_contract_claim_issue(_{test_id: TestString, claim_key: KeyString, reason:
|
|
|
352
648
|
|
|
353
649
|
%% proof_contract_claim_candidates_json(-Json)
|
|
354
650
|
% For the schema 12 migration: one row {test_id, kind, candidates} per
|
|
355
|
-
% certificate-kind proof contract without proves
|
|
356
|
-
% claims {claim_key, requirement_id, fact_id} of
|
|
357
|
-
%
|
|
358
|
-
%
|
|
651
|
+
% certificate-kind proof contract without proves. Candidates are the
|
|
652
|
+
% modeled claims {claim_key, requirement_id, fact_id, binding, reason?} of
|
|
653
|
+
% current requirements the test reaches through its scenarios, with the
|
|
654
|
+
% binding a certificate of its kind would get: derived when the grounding
|
|
655
|
+
% fact derives an obligation for the kind, attested when the fact has no
|
|
656
|
+
% derivation, incompatible (with the reason) when the kind cannot express
|
|
657
|
+
% it. The migration pins proves only when exactly one claim is derived;
|
|
658
|
+
% the review lists every candidate so a person can bind the rest.
|
|
359
659
|
proof_contract_claim_candidates_json(Json) :-
|
|
360
660
|
findall(Row, proof_contract_claim_candidates(Row), Rows0),
|
|
361
661
|
sort(Rows0, Rows),
|
|
@@ -364,15 +664,43 @@ proof_contract_claim_candidates_json(Json) :-
|
|
|
364
664
|
proof_contract_claim_candidates(_{test_id: TestString, kind: KindString, candidates: Candidates}) :-
|
|
365
665
|
test_certificate_contract(TestId, Kind, Contract),
|
|
366
666
|
\+ ( get_dict(proves, Contract, Proves), is_list(Proves), Proves \== [] ),
|
|
367
|
-
findall(
|
|
667
|
+
findall(Candidate,
|
|
368
668
|
( test_requirement(TestId, ReqId),
|
|
369
669
|
requirement_claim_status(ReqId, ClaimKey, modeled),
|
|
370
|
-
claim_obligation(ReqId-ClaimKey, Kind,
|
|
371
|
-
|
|
372
|
-
atom_string(ReqId, ReqString),
|
|
373
|
-
atom_string(FactId, FactString)
|
|
670
|
+
claim_obligation(ReqId-ClaimKey, Kind, Result),
|
|
671
|
+
claim_candidate(ReqId, ClaimKey, Result, Candidate)
|
|
374
672
|
),
|
|
375
673
|
Candidates0),
|
|
376
|
-
sort(Candidates0,
|
|
674
|
+
sort(Candidates0, Candidates1),
|
|
675
|
+
sort(claim_key, @=<, Candidates1, Candidates),
|
|
377
676
|
atom_string(TestId, TestString),
|
|
378
677
|
atom_string(Kind, KindString).
|
|
678
|
+
|
|
679
|
+
claim_candidate(ReqId, ClaimKey, Result, Candidate) :-
|
|
680
|
+
( Result = obligation(FactId, _)
|
|
681
|
+
-> Binding = "derived"
|
|
682
|
+
; Result = attested(FactId)
|
|
683
|
+
-> Binding = "attested"
|
|
684
|
+
; Result = incompatible(FactId, Reason)
|
|
685
|
+
-> Binding = "incompatible"
|
|
686
|
+
),
|
|
687
|
+
atom_string(ClaimKey, KeyString),
|
|
688
|
+
atom_string(ReqId, ReqString),
|
|
689
|
+
atom_string(FactId, FactString),
|
|
690
|
+
Base = _{claim_key: KeyString, requirement_id: ReqString, fact_id: FactString, binding: Binding},
|
|
691
|
+
( var(Reason) -> Candidate = Base ; Candidate = Base.put(reason, Reason) ).
|
|
692
|
+
|
|
693
|
+
%% claim_model_check_json(+Kind, +ObligationJson, +CertificateJson, -Json)
|
|
694
|
+
% For `kibi prove`: whether a rule or state_machine certificate's model meets
|
|
695
|
+
% one derived obligation, {"ok": true} or {"ok": false, "reason": R}.
|
|
696
|
+
claim_model_check_json(Kind0, ObligationJson, CertificateJson, Json) :-
|
|
697
|
+
kb:normalize_term_atom(Kind0, Kind),
|
|
698
|
+
atom_json_dict(ObligationJson, Obligation, [value_string_as(string)]),
|
|
699
|
+
atom_json_dict(CertificateJson, Certificate, [value_string_as(string)]),
|
|
700
|
+
model_check_obligation(Kind, Obligation, Certificate, Result),
|
|
701
|
+
( Result == ok
|
|
702
|
+
-> Out = _{ok: true}
|
|
703
|
+
; Result = problem(Reason),
|
|
704
|
+
Out = _{ok: false, reason: Reason}
|
|
705
|
+
),
|
|
706
|
+
with_output_to(string(Json), json_write_dict(current_output, Out, [width(0)])).
|