kibi-core 0.23.0 → 0.24.0
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 +6 -0
- package/schema/validation.pl +4 -1
- package/src/certificate_check.pl +28 -1
- package/src/certificates.pl +9 -1
- package/src/checks.pl +4 -1
- package/src/claim_obligations.pl +378 -0
- package/src/code_certificates.pl +297 -0
- package/src/discovery.pl +160 -2
- package/src/intervals.pl +11 -0
- package/src/kb.pl +7 -0
- package/src/requirement_proof.pl +554 -42
- package/src/rule_registry.pl +8 -0
- package/src/semantic_quality.pl +1 -1
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
% Module: code_certificates
|
|
2
|
+
% implements REQ-kibi-code-proof-kinds
|
|
3
|
+
%
|
|
4
|
+
% Re-checks the kibi.code-certificate.v1 a certificate-kind proof contract
|
|
5
|
+
% leaves in its receipt. A receipt counts as proved only when its
|
|
6
|
+
% certificate re-checks here, so a test that claims to have covered every
|
|
7
|
+
% case cannot earn proved by saying so. The checks never search:
|
|
8
|
+
%
|
|
9
|
+
% exhaustive The domain lists at least one member, no member twice, and
|
|
10
|
+
% the results name every member exactly once, each passed.
|
|
11
|
+
% (Kibi checks at ingest that the proof imported the domain
|
|
12
|
+
% from its source instead of hand-listing it.)
|
|
13
|
+
% typecheck The witness names a file and its sha256 and the compiler
|
|
14
|
+
% that accepted it; the outcome is passed. (Kibi checks at
|
|
15
|
+
% ingest that the witness is the file in the workspace and
|
|
16
|
+
% imports the domain.)
|
|
17
|
+
% arithmetic The assumptions on one variable are satisfiable and entail
|
|
18
|
+
% the claim, decided exactly by intervals.pl: the assumptions
|
|
19
|
+
% plus the negated claim admit no value.
|
|
20
|
+
% smt The query asks (check-sat) and the solver answered unsat.
|
|
21
|
+
% (Kibi re-runs the solver on the query at ingest.)
|
|
22
|
+
%
|
|
23
|
+
% The digest a receipt records is the sha256 of the certificate's canonical
|
|
24
|
+
% JSON as canonical_json/2 below spells it. The CLI records the digest this
|
|
25
|
+
% module returns at ingest, so ingest and every later re-check hash the
|
|
26
|
+
% certificate the same way.
|
|
27
|
+
|
|
28
|
+
:- module(code_certificates, [
|
|
29
|
+
code_certificate_check/2,
|
|
30
|
+
code_certificate_digest/2,
|
|
31
|
+
code_certificate_check_json/2
|
|
32
|
+
]).
|
|
33
|
+
|
|
34
|
+
:- use_module(library(http/json)).
|
|
35
|
+
:- use_module(library(crypto)).
|
|
36
|
+
:- use_module(library(apply)).
|
|
37
|
+
:- use_module(library(lists)).
|
|
38
|
+
:- use_module('intervals.pl', [
|
|
39
|
+
numeric_constraints_satisfiable/2,
|
|
40
|
+
numeric_constraint_entailed/3
|
|
41
|
+
]).
|
|
42
|
+
|
|
43
|
+
code_certificate_version("kibi.code-certificate.v1").
|
|
44
|
+
|
|
45
|
+
%% code_certificate_check(+Certificate, -Result)
|
|
46
|
+
% Result is ok or rejected(Reason) with Reason a string naming what failed.
|
|
47
|
+
code_certificate_check(Certificate, Result) :-
|
|
48
|
+
( catch(certificate_problem(Certificate, Reason), _, (Reason = "the certificate is malformed"))
|
|
49
|
+
-> Result = rejected(Reason)
|
|
50
|
+
; Result = ok
|
|
51
|
+
).
|
|
52
|
+
|
|
53
|
+
%% code_certificate_digest(+Certificate, -Digest)
|
|
54
|
+
code_certificate_digest(Certificate, Digest) :-
|
|
55
|
+
canonical_json(Certificate, Json),
|
|
56
|
+
crypto_data_hash(Json, Digest, [algorithm(sha256), encoding(utf8)]).
|
|
57
|
+
|
|
58
|
+
%% code_certificate_check_json(+CertificateJson, -ResultJson)
|
|
59
|
+
% Entry point for the CLI: {"accepted": true, "digest": D} or
|
|
60
|
+
% {"accepted": false, "reason": R}.
|
|
61
|
+
code_certificate_check_json(CertificateJson, ResultJson) :-
|
|
62
|
+
( catch(atom_json_dict(CertificateJson, Certificate, [value_string_as(string)]), _, fail),
|
|
63
|
+
is_dict(Certificate)
|
|
64
|
+
-> code_certificate_check(Certificate, Result),
|
|
65
|
+
code_certificate_digest(Certificate, Digest),
|
|
66
|
+
( Result == ok
|
|
67
|
+
-> Out = _{accepted: true, digest: Digest}
|
|
68
|
+
; Result = rejected(Reason),
|
|
69
|
+
Out = _{accepted: false, digest: Digest, reason: Reason}
|
|
70
|
+
)
|
|
71
|
+
; Out = _{accepted: false, reason: "the certificate is not a JSON object"}
|
|
72
|
+
),
|
|
73
|
+
with_output_to(string(ResultJson), json_write_dict(current_output, Out, [width(0)])).
|
|
74
|
+
|
|
75
|
+
% certificate_problem(+Certificate, -Reason) succeeds with the first problem.
|
|
76
|
+
certificate_problem(Certificate, Reason) :-
|
|
77
|
+
\+ is_dict(Certificate), !,
|
|
78
|
+
Reason = "the certificate is not an object".
|
|
79
|
+
certificate_problem(Certificate, Reason) :-
|
|
80
|
+
code_certificate_version(Version),
|
|
81
|
+
\+ get_dict(version, Certificate, Version), !,
|
|
82
|
+
format(string(Reason), "the certificate version is not ~w", [Version]).
|
|
83
|
+
certificate_problem(Certificate, Reason) :-
|
|
84
|
+
\+ (get_dict(test_id, Certificate, TestId), nonempty_string(TestId)), !,
|
|
85
|
+
Reason = "the certificate names no test_id".
|
|
86
|
+
certificate_problem(Certificate, Reason) :-
|
|
87
|
+
( get_dict(kind, Certificate, KindString), string(KindString)
|
|
88
|
+
-> atom_string(Kind, KindString)
|
|
89
|
+
; Kind = none
|
|
90
|
+
),
|
|
91
|
+
( kind_problem(Kind, Certificate, Reason0)
|
|
92
|
+
-> Reason = Reason0
|
|
93
|
+
; \+ memberchk(Kind, [exhaustive, typecheck, arithmetic, smt])
|
|
94
|
+
-> Reason = "the certificate kind is not exhaustive, typecheck, arithmetic or smt"
|
|
95
|
+
).
|
|
96
|
+
|
|
97
|
+
kind_problem(exhaustive, Certificate, Reason) :-
|
|
98
|
+
field(Certificate, domain, Domain),
|
|
99
|
+
field(Domain, symbol_id, Symbol),
|
|
100
|
+
field(Domain, members, Members),
|
|
101
|
+
field(Certificate, results, Results),
|
|
102
|
+
( \+ is_dict(Domain)
|
|
103
|
+
-> Reason = "the certificate has no domain"
|
|
104
|
+
; \+ nonempty_string(Symbol)
|
|
105
|
+
-> Reason = "the domain names no symbol_id"
|
|
106
|
+
; \+ (is_list(Members), Members \== [])
|
|
107
|
+
-> Reason = "the domain lists no members"
|
|
108
|
+
; \+ maplist(nonempty_string, Members)
|
|
109
|
+
-> Reason = "every domain member must be a non-empty string"
|
|
110
|
+
; msort(Members, Sorted), sort(Members, Unique), Sorted \== Unique
|
|
111
|
+
-> Reason = "the domain lists a member twice"
|
|
112
|
+
; \+ is_list(Results)
|
|
113
|
+
-> Reason = "the certificate has no results"
|
|
114
|
+
; \+ maplist(result_row, Results)
|
|
115
|
+
-> Reason = "every result must name a member and an outcome"
|
|
116
|
+
; findall(M, (member(Row, Results), get_dict(member, Row, M)), Named),
|
|
117
|
+
msort(Named, NamedSorted),
|
|
118
|
+
msort(Members, MembersSorted),
|
|
119
|
+
NamedSorted \== MembersSorted
|
|
120
|
+
-> Reason = "the results do not name every domain member exactly once"
|
|
121
|
+
; member(Row, Results),
|
|
122
|
+
get_dict(outcome, Row, Outcome),
|
|
123
|
+
Outcome \== "passed"
|
|
124
|
+
-> get_dict(member, Row, Failed),
|
|
125
|
+
format(string(Reason), "member ~w did not pass", [Failed])
|
|
126
|
+
).
|
|
127
|
+
kind_problem(typecheck, Certificate, Reason) :-
|
|
128
|
+
field(Certificate, witness, Witness),
|
|
129
|
+
field(Witness, path, Path),
|
|
130
|
+
field(Witness, sha256, Hash),
|
|
131
|
+
field(Certificate, domain, Domain),
|
|
132
|
+
field(Domain, symbol_id, Symbol),
|
|
133
|
+
field(Certificate, compiler, Compiler),
|
|
134
|
+
field(Compiler, name, Name),
|
|
135
|
+
field(Compiler, version, CompilerVersion),
|
|
136
|
+
field(Certificate, outcome, Outcome),
|
|
137
|
+
( \+ nonempty_string(Path)
|
|
138
|
+
-> Reason = "the certificate names no witness path"
|
|
139
|
+
; \+ sha256_string(Hash)
|
|
140
|
+
-> Reason = "the witness has no sha256"
|
|
141
|
+
; \+ nonempty_string(Symbol)
|
|
142
|
+
-> Reason = "the certificate names no domain symbol_id"
|
|
143
|
+
; \+ (nonempty_string(Name), nonempty_string(CompilerVersion))
|
|
144
|
+
-> Reason = "the certificate names no compiler and version"
|
|
145
|
+
; Outcome \== "passed"
|
|
146
|
+
-> Reason = "the type checker did not accept the witness"
|
|
147
|
+
).
|
|
148
|
+
kind_problem(arithmetic, Certificate, Reason) :-
|
|
149
|
+
field(Certificate, variable, Variable),
|
|
150
|
+
field(Certificate, value_type, Type),
|
|
151
|
+
field(Certificate, assumptions, Assumptions),
|
|
152
|
+
field(Certificate, claim, Claim),
|
|
153
|
+
( \+ nonempty_string(Variable)
|
|
154
|
+
-> Reason = "the certificate names no variable"
|
|
155
|
+
; \+ memberchk(Type, ["int", "real"])
|
|
156
|
+
-> Reason = "value_type must be int or real"
|
|
157
|
+
; \+ (is_list(Assumptions), Assumptions \== [])
|
|
158
|
+
-> Reason = "the certificate states no assumptions"
|
|
159
|
+
; \+ maplist(arithmetic_constraint(_), Assumptions, _)
|
|
160
|
+
-> Reason = "every assumption must be {operator, value} with a comparison operator and a number"
|
|
161
|
+
; \+ arithmetic_constraint(_, Claim, _)
|
|
162
|
+
-> Reason = "the claim must be {operator, value} with a comparison operator and a number"
|
|
163
|
+
; maplist(arithmetic_constraint(X), Assumptions, Premises),
|
|
164
|
+
arithmetic_constraint(X, Claim, Goal),
|
|
165
|
+
integer_variables(Type, X, Integers),
|
|
166
|
+
( \+ numeric_constraints_satisfiable(Premises, Integers)
|
|
167
|
+
-> Reason = "the assumptions admit no value, so the claim holds vacuously"
|
|
168
|
+
; \+ numeric_constraint_entailed(Premises, Goal, Integers)
|
|
169
|
+
-> Reason = "the assumptions do not entail the claim: some value satisfies them and breaks it"
|
|
170
|
+
)
|
|
171
|
+
).
|
|
172
|
+
kind_problem(smt, Certificate, Reason) :-
|
|
173
|
+
field(Certificate, solver, Solver),
|
|
174
|
+
field(Solver, name, Name),
|
|
175
|
+
field(Solver, version, SolverVersion),
|
|
176
|
+
field(Certificate, query, Query),
|
|
177
|
+
field(Certificate, result, Result),
|
|
178
|
+
( \+ (nonempty_string(Name), nonempty_string(SolverVersion))
|
|
179
|
+
-> Reason = "the certificate names no solver and version"
|
|
180
|
+
; \+ nonempty_string(Query)
|
|
181
|
+
-> Reason = "the certificate has no query"
|
|
182
|
+
; \+ sub_string(Query, _, _, _, "(check-sat)")
|
|
183
|
+
-> Reason = "the query never asks (check-sat)"
|
|
184
|
+
; Result \== "unsat"
|
|
185
|
+
-> Reason = "the solver did not answer unsat"
|
|
186
|
+
).
|
|
187
|
+
|
|
188
|
+
% field(+Dict, +Key, -Value): the value, or '$missing' when absent.
|
|
189
|
+
field(Dict, Key, Value) :-
|
|
190
|
+
( is_dict(Dict), get_dict(Key, Dict, Value0)
|
|
191
|
+
-> Value = Value0
|
|
192
|
+
; Value = '$missing'
|
|
193
|
+
).
|
|
194
|
+
|
|
195
|
+
result_row(Row) :-
|
|
196
|
+
is_dict(Row),
|
|
197
|
+
get_dict(member, Row, _),
|
|
198
|
+
get_dict(outcome, Row, _).
|
|
199
|
+
|
|
200
|
+
arithmetic_constraint(X, Row, c(Op, X, Value)) :-
|
|
201
|
+
is_dict(Row),
|
|
202
|
+
get_dict(operator, Row, OpString),
|
|
203
|
+
string(OpString),
|
|
204
|
+
atom_string(Op, OpString),
|
|
205
|
+
memberchk(Op, [eq, neq, lt, lte, gt, gte]),
|
|
206
|
+
get_dict(value, Row, Value),
|
|
207
|
+
number(Value).
|
|
208
|
+
|
|
209
|
+
integer_variables("int", X, [X]).
|
|
210
|
+
integer_variables("real", _, []).
|
|
211
|
+
|
|
212
|
+
nonempty_string(Value) :-
|
|
213
|
+
string(Value),
|
|
214
|
+
Value \== "".
|
|
215
|
+
|
|
216
|
+
sha256_string(Value) :-
|
|
217
|
+
string(Value),
|
|
218
|
+
string_length(Value, 64),
|
|
219
|
+
string_codes(Value, Codes),
|
|
220
|
+
forall(member(C, Codes), (code_type(C, digit) ; between(0'a, 0'f, C))).
|
|
221
|
+
|
|
222
|
+
% The canonical JSON is spelled out here rather than left to json_write, so
|
|
223
|
+
% it does not depend on the stream encoding or on how a key was read (a YAML
|
|
224
|
+
% key and a JSON key for the same text are the same key): object keys sorted
|
|
225
|
+
% by code point, no whitespace, ", \ and the JSON control escapes, other
|
|
226
|
+
% control characters and every non-ASCII character as \u escapes of their
|
|
227
|
+
% UTF-16 code units, integers in decimal.
|
|
228
|
+
canonical_json(Value, Json) :-
|
|
229
|
+
is_dict(Value), !,
|
|
230
|
+
dict_pairs(Value, _, Pairs0),
|
|
231
|
+
maplist(string_keyed, Pairs0, Pairs1),
|
|
232
|
+
keysort(Pairs1, Pairs),
|
|
233
|
+
maplist(canonical_pair, Pairs, Parts),
|
|
234
|
+
atomics_to_string(Parts, ",", Body),
|
|
235
|
+
format(string(Json), "{~s}", [Body]).
|
|
236
|
+
canonical_json(Value, Json) :-
|
|
237
|
+
is_list(Value), !,
|
|
238
|
+
maplist(canonical_json, Value, Parts),
|
|
239
|
+
atomics_to_string(Parts, ",", Body),
|
|
240
|
+
format(string(Json), "[~s]", [Body]).
|
|
241
|
+
canonical_json(Value, Json) :-
|
|
242
|
+
string(Value), !,
|
|
243
|
+
canonical_string(Value, Json).
|
|
244
|
+
canonical_json(Value, Json) :-
|
|
245
|
+
integer(Value), !,
|
|
246
|
+
format(string(Json), "~d", [Value]).
|
|
247
|
+
canonical_json(Value, Json) :-
|
|
248
|
+
memberchk(Value, [true, false, null]), !,
|
|
249
|
+
atom_string(Value, Json).
|
|
250
|
+
canonical_json(Value, Json) :-
|
|
251
|
+
atom(Value), !,
|
|
252
|
+
atom_string(Value, String),
|
|
253
|
+
canonical_string(String, Json).
|
|
254
|
+
canonical_json(Value, Json) :-
|
|
255
|
+
with_output_to(string(Json), json_write(current_output, Value, [width(0)])).
|
|
256
|
+
|
|
257
|
+
% Keys as code lists, so keysort orders them by code point whatever the
|
|
258
|
+
% reader made of them (an atom, or a small integer for a numeric key).
|
|
259
|
+
string_keyed(Key-Value, Codes-Value) :-
|
|
260
|
+
format(string(String), "~w", [Key]),
|
|
261
|
+
string_codes(String, Codes).
|
|
262
|
+
|
|
263
|
+
canonical_pair(Codes-Value, Json) :-
|
|
264
|
+
string_codes(Key, Codes),
|
|
265
|
+
canonical_string(Key, KeyJson),
|
|
266
|
+
canonical_json(Value, ValueJson),
|
|
267
|
+
format(string(Json), "~s:~s", [KeyJson, ValueJson]).
|
|
268
|
+
|
|
269
|
+
canonical_string(String, Json) :-
|
|
270
|
+
string_codes(String, Codes),
|
|
271
|
+
phrase(escaped_codes(Codes), Escaped),
|
|
272
|
+
string_codes(Body, Escaped),
|
|
273
|
+
format(string(Json), "\"~s\"", [Body]).
|
|
274
|
+
|
|
275
|
+
escaped_codes([]) --> [].
|
|
276
|
+
escaped_codes([C|Cs]) --> escaped_code(C), escaped_codes(Cs).
|
|
277
|
+
|
|
278
|
+
escaped_code(0'") --> !, "\\\"".
|
|
279
|
+
escaped_code(0'\\) --> !, "\\\\".
|
|
280
|
+
escaped_code(0'\b) --> !, "\\b".
|
|
281
|
+
escaped_code(0'\f) --> !, "\\f".
|
|
282
|
+
escaped_code(0'\n) --> !, "\\n".
|
|
283
|
+
escaped_code(0'\r) --> !, "\\r".
|
|
284
|
+
escaped_code(0'\t) --> !, "\\t".
|
|
285
|
+
escaped_code(C) --> { C >= 0x20, C =< 0x7e }, !, [C].
|
|
286
|
+
escaped_code(C) -->
|
|
287
|
+
{ C > 0xffff }, !,
|
|
288
|
+
{ V is C - 0x10000,
|
|
289
|
+
High is 0xd800 + (V >> 10),
|
|
290
|
+
Low is 0xdc00 + (V /\ 0x3ff) },
|
|
291
|
+
unit_escape(High),
|
|
292
|
+
unit_escape(Low).
|
|
293
|
+
escaped_code(C) --> unit_escape(C).
|
|
294
|
+
|
|
295
|
+
unit_escape(Unit) -->
|
|
296
|
+
{ format(codes(Codes), "\\u~|~`0t~16r~4+", [Unit]) },
|
|
297
|
+
Codes.
|
package/src/discovery.pl
CHANGED
|
@@ -7,10 +7,13 @@
|
|
|
7
7
|
coverage_report_json/10,
|
|
8
8
|
coverage_report_json/11,
|
|
9
9
|
coverage_report_json/12,
|
|
10
|
+
coverage_report_json/13,
|
|
10
11
|
coverage_evidence_json/5,
|
|
12
|
+
evidence_floor_json/6,
|
|
11
13
|
requirement_proof_json/5,
|
|
12
14
|
requirement_proof_json/7,
|
|
13
15
|
symbol_proof_json/7,
|
|
16
|
+
test_receipt_states_json/6,
|
|
14
17
|
graph_expand_json/8,
|
|
15
18
|
search_answer_verdicts_json/2,
|
|
16
19
|
ask_predicate_json/9,
|
|
@@ -29,7 +32,7 @@
|
|
|
29
32
|
:- use_module(library(pairs)).
|
|
30
33
|
:- use_module('kb.pl').
|
|
31
34
|
:- use_module(library(semweb/rdf_db), [rdf_generation/1]).
|
|
32
|
-
:- use_module('requirement_proof.pl', [requirement_proof_context/1, requirement_proof_context/4, requirement_proof_context/6, requirement_proof/4]).
|
|
35
|
+
:- use_module('requirement_proof.pl', [requirement_proof_context/1, requirement_proof_context/4, requirement_proof_context/6, requirement_proof/4, test_receipt_evidence/4]).
|
|
33
36
|
:- use_module('checks.pl', [scenario_feasibility_outcome/2, infeasible_scenario/5]).
|
|
34
37
|
:- use_module('logic_ir.pl', [logic_rule_conflict/3, logic_rule_conflict/4]).
|
|
35
38
|
:- use_module('status.pl', [status_meta_dict/1, status_scope_dict/1]).
|
|
@@ -78,6 +81,21 @@ requirement_proof_json(ReqId, BindingMode, TestBindings, VerificationSnapshot, C
|
|
|
78
81
|
put_dict(id, Proof, ReqId, ProofWithId),
|
|
79
82
|
dict_json_string(ProofWithId, JsonString).
|
|
80
83
|
|
|
84
|
+
% implements REQ-kibi-verification-evidence-contract
|
|
85
|
+
% The receipt state of every proof-bearing test under the given binding, as
|
|
86
|
+
% `kibi prove --stale` selects them: passed means a current passing receipt.
|
|
87
|
+
test_receipt_states_json(BindingMode, TestBindings, VerificationSnapshot, CheckedAt, MaxAgeSeconds, JsonString) :-
|
|
88
|
+
requirement_proof_context(VerificationSnapshot, CheckedAt, MaxAgeSeconds, BindingMode, TestBindings, Context),
|
|
89
|
+
findall(TestId-State,
|
|
90
|
+
(kb_entity(TestId, test, Props),
|
|
91
|
+
memberchk(proof_contract=_, Props),
|
|
92
|
+
test_receipt_evidence(Context, [unit, integration, end_to_end], TestId, Evidence),
|
|
93
|
+
State = Evidence.state),
|
|
94
|
+
Pairs0),
|
|
95
|
+
sort(Pairs0, Pairs),
|
|
96
|
+
dict_pairs(States, _, Pairs),
|
|
97
|
+
dict_json_string(_{states: States}, JsonString).
|
|
98
|
+
|
|
81
99
|
symbol_proof_json(SymbolId, BindingMode, TestBindings, VerificationSnapshot, CheckedAt, MaxAgeSeconds, JsonString) :-
|
|
82
100
|
requirement_proof_context(VerificationSnapshot, CheckedAt, MaxAgeSeconds, BindingMode, TestBindings, Context),
|
|
83
101
|
kb_entity(SymbolId, symbol, _),
|
|
@@ -344,6 +362,77 @@ coverage_evidence_json(Tags, VerificationSnapshot, CheckedAt, MaxAgeSeconds, Jso
|
|
|
344
362
|
Response = _{rows: Evidences},
|
|
345
363
|
dict_json_string(Response, JsonString).
|
|
346
364
|
|
|
365
|
+
% implements REQ-kibi-evidence-floors
|
|
366
|
+
% Every current requirement whose evidence level is below its floor, for the
|
|
367
|
+
% evidence-floor check rule: the rows that fail (status below) or that only
|
|
368
|
+
% need kibi prove re-run (status pending_reproof), plus how many current
|
|
369
|
+
% requirements were evaluated. Requirements outside proof scope carry no
|
|
370
|
+
% floor.
|
|
371
|
+
evidence_floor_json(BindingMode, TestBindings, VerificationSnapshot, CheckedAt, MaxAgeSeconds, JsonString) :-
|
|
372
|
+
requirement_proof_context(VerificationSnapshot, CheckedAt, MaxAgeSeconds, BindingMode, TestBindings, Context),
|
|
373
|
+
findall(Id-Proof,
|
|
374
|
+
( kb_entity(Id, req, Props),
|
|
375
|
+
requirement_proof(Id, Props, Context, Proof),
|
|
376
|
+
get_dict(evidenceFloor, Proof, _)
|
|
377
|
+
),
|
|
378
|
+
Evaluated),
|
|
379
|
+
findall(Row,
|
|
380
|
+
( member(Id-Proof, Evaluated),
|
|
381
|
+
Floor = Proof.evidenceFloor,
|
|
382
|
+
Floor.status \== met,
|
|
383
|
+
Row = _{
|
|
384
|
+
id: Id,
|
|
385
|
+
evidenceLevel: Proof.evidenceLevel,
|
|
386
|
+
requiredEvidence: Floor.required,
|
|
387
|
+
requiredEvidenceSource: Floor.source,
|
|
388
|
+
status: Floor.status,
|
|
389
|
+
proofStatus: Proof.proofStatus,
|
|
390
|
+
proofGaps: Proof.proofGaps,
|
|
391
|
+
gapDetails: Details
|
|
392
|
+
},
|
|
393
|
+
evidence_floor_gap_details(Proof, Details)
|
|
394
|
+
),
|
|
395
|
+
Rows0),
|
|
396
|
+
sort(id, @<, Rows0, Rows),
|
|
397
|
+
length(Evaluated, Count),
|
|
398
|
+
dict_json_string(_{evaluated: Count, rows: Rows}, JsonString).
|
|
399
|
+
|
|
400
|
+
% The entities behind an evidence-floor row's gaps, so a person sees what to
|
|
401
|
+
% fix without one kibi proof explain per requirement: the tests whose
|
|
402
|
+
% receipts are missing, stale, failed or invalid, the production symbols no
|
|
403
|
+
% qualifying test covers and the symbols without source coordinates. Empty
|
|
404
|
+
% lists are left out.
|
|
405
|
+
evidence_floor_gap_details(Proof, Details) :-
|
|
406
|
+
( get_dict(proofStages, Proof, Stages)
|
|
407
|
+
-> true
|
|
408
|
+
; Stages = _{}
|
|
409
|
+
),
|
|
410
|
+
findall(Key-Ids,
|
|
411
|
+
( evidence_floor_gap_field(Key, StageKey, Field),
|
|
412
|
+
get_dict(StageKey, Stages, Stage),
|
|
413
|
+
get_dict(Field, Stage, Ids),
|
|
414
|
+
Ids \== []
|
|
415
|
+
),
|
|
416
|
+
Pairs0),
|
|
417
|
+
% implements REQ-kibi-claim-bound-proofs
|
|
418
|
+
% Under a proved floor, the claims no certificate binding derives.
|
|
419
|
+
( get_dict(evidenceFloor, Proof, Floor),
|
|
420
|
+
get_dict(required, Floor, proved),
|
|
421
|
+
get_dict(unprovedClaims, Proof, Unproved),
|
|
422
|
+
Unproved \== []
|
|
423
|
+
-> Pairs = [unprovedClaims-Unproved|Pairs0]
|
|
424
|
+
; Pairs = Pairs0
|
|
425
|
+
),
|
|
426
|
+
dict_pairs(Details, _, Pairs).
|
|
427
|
+
|
|
428
|
+
evidence_floor_gap_field(failedReceiptTests, passingE2e, failedReceiptTests).
|
|
429
|
+
evidence_floor_gap_field(missingReceiptTests, passingE2e, missingReceiptTests).
|
|
430
|
+
evidence_floor_gap_field(staleReceiptTests, passingE2e, staleReceiptTests).
|
|
431
|
+
evidence_floor_gap_field(invalidReceiptTests, passingE2e, invalidReceiptTests).
|
|
432
|
+
evidence_floor_gap_field(contractMismatchReceiptTests, passingE2e, contractMismatchReceiptTests).
|
|
433
|
+
evidence_floor_gap_field(uncoveredSymbols, productionSymbols, uncoveredSymbols).
|
|
434
|
+
evidence_floor_gap_field(missingCoordinateSymbols, sourceCoordinates, missingSymbols).
|
|
435
|
+
|
|
347
436
|
coverage_evidence_row(Id, Proof, Evidence) :-
|
|
348
437
|
( get_dict(proofStatus, Proof, ProofStatus)
|
|
349
438
|
-> true
|
|
@@ -414,6 +503,28 @@ coverage_report_json(By, Tags, IncludePassing, StatusFilter, IncludeTransitive,
|
|
|
414
503
|
Response = _{summary: Summary, rows: Rows, meta: Meta},
|
|
415
504
|
dict_json_string(Response, JsonString).
|
|
416
505
|
|
|
506
|
+
% Proof-status filtered report with per-contract receipt binding: the same
|
|
507
|
+
% rows and whole-KB summary as the unfiltered report under that binding mode,
|
|
508
|
+
% so a status filter never judges receipts more strictly than kb_coverage,
|
|
509
|
+
% kibi proof explain or the evidence-floor rule do.
|
|
510
|
+
coverage_report_json(By, Tags, IncludePassing, StatusFilter, BindingMode, TestBindings, IncludeTransitive, Limit, Offset, VerificationSnapshot, CheckedAt, MaxAgeSeconds, JsonString) :-
|
|
511
|
+
memo_sorted_coverage(
|
|
512
|
+
status_binding(By, Tags, IncludePassing, StatusFilter, BindingMode, TestBindings, IncludeTransitive, VerificationSnapshot, CheckedAt, MaxAgeSeconds),
|
|
513
|
+
status_binding_coverage_rows(By, Tags, IncludePassing, StatusFilter, BindingMode, TestBindings, IncludeTransitive, VerificationSnapshot, CheckedAt, MaxAgeSeconds),
|
|
514
|
+
SortedRows,
|
|
515
|
+
Summary),
|
|
516
|
+
paginate_rows(SortedRows, Offset, Limit, Rows),
|
|
517
|
+
status_meta_dict(Meta),
|
|
518
|
+
Response = _{summary: Summary, rows: Rows, meta: Meta},
|
|
519
|
+
dict_json_string(Response, JsonString).
|
|
520
|
+
|
|
521
|
+
status_binding_coverage_rows(req, Tags, _IncludePassing, StatusFilter, BindingMode, TestBindings, IncludeTransitive, VerificationSnapshot, CheckedAt, MaxAgeSeconds, MatchedRows, Summary) :-
|
|
522
|
+
!,
|
|
523
|
+
coverage_rows(req, Tags, true, IncludeTransitive, VerificationSnapshot, CheckedAt, MaxAgeSeconds, BindingMode, TestBindings, Rows0, Summary),
|
|
524
|
+
include(req_row_proof_status_in(StatusFilter), Rows0, MatchedRows).
|
|
525
|
+
status_binding_coverage_rows(By, Tags, IncludePassing, StatusFilter, _BindingMode, _TestBindings, IncludeTransitive, VerificationSnapshot, CheckedAt, MaxAgeSeconds, Rows, Summary) :-
|
|
526
|
+
status_filtered_coverage_rows(By, Tags, IncludePassing, StatusFilter, IncludeTransitive, VerificationSnapshot, CheckedAt, MaxAgeSeconds, Rows, Summary).
|
|
527
|
+
|
|
417
528
|
binding_coverage_rows(req, Tags, IncludePassing, BindingMode, TestBindings, IncludeTransitive, VerificationSnapshot, CheckedAt, MaxAgeSeconds, Rows, Summary) :-
|
|
418
529
|
!,
|
|
419
530
|
coverage_rows(req, Tags, IncludePassing, IncludeTransitive, VerificationSnapshot, CheckedAt, MaxAgeSeconds, BindingMode, TestBindings, Rows, Summary).
|
|
@@ -1118,6 +1229,10 @@ symbol_coverage_row(Tags, Row) :-
|
|
|
1118
1229
|
coverageStatus: CoverageStatus
|
|
1119
1230
|
}.
|
|
1120
1231
|
|
|
1232
|
+
% Link coverage (evaluated, fullyCovered, uncovered, notApplicable and the
|
|
1233
|
+
% missing* counts) judges only priority: must requirements; linkCoverageScope
|
|
1234
|
+
% says so, so a project without must requirements does not read as having
|
|
1235
|
+
% evaluated nothing next to its proof counts.
|
|
1121
1236
|
coverage_summary(Rows, Summary) :-
|
|
1122
1237
|
length(Rows, Total),
|
|
1123
1238
|
include(req_row_evaluated, Rows, EvaluatedRows),
|
|
@@ -1140,6 +1255,8 @@ coverage_summary(Rows, Summary) :-
|
|
|
1140
1255
|
length(MissingBothRows, MissingScenarioAndTest),
|
|
1141
1256
|
length(ProofTestedRows, ProofTested),
|
|
1142
1257
|
evidence_level_counts(Rows, EvidenceLevels),
|
|
1258
|
+
claim_level_counts(Rows, ProofClaims),
|
|
1259
|
+
evidence_floor_counts(Rows, EvidenceFloors),
|
|
1143
1260
|
length(ProofUnresolvedRows, ProofUnresolved),
|
|
1144
1261
|
length(ProofMissingRows, ProofMissing),
|
|
1145
1262
|
length(ProofNotApplicableRows, ProofNotApplicable),
|
|
@@ -1156,9 +1273,50 @@ coverage_summary(Rows, Summary) :-
|
|
|
1156
1273
|
proofUnresolved: ProofUnresolved,
|
|
1157
1274
|
proofMissing: ProofMissing,
|
|
1158
1275
|
proofNotApplicable: ProofNotApplicable,
|
|
1159
|
-
evidenceLevels: EvidenceLevels
|
|
1276
|
+
evidenceLevels: EvidenceLevels,
|
|
1277
|
+
proofClaims: ProofClaims,
|
|
1278
|
+
evidenceFloors: EvidenceFloors,
|
|
1279
|
+
linkCoverageScope: priority_must
|
|
1160
1280
|
}.
|
|
1161
1281
|
|
|
1282
|
+
% implements REQ-kibi-claim-bound-proofs
|
|
1283
|
+
% The applicable requirement claims across Rows, by the level their evidence
|
|
1284
|
+
% reaches: a claim is proved only through a derived certificate binding.
|
|
1285
|
+
claim_level_counts(Rows, _{applicable: Applicable, proved: Proved, attested: Attested,
|
|
1286
|
+
tested: Tested, unresolved: Unresolved}) :-
|
|
1287
|
+
claim_count_sum(Rows, applicable, Applicable),
|
|
1288
|
+
claim_count_sum(Rows, proved, Proved),
|
|
1289
|
+
claim_count_sum(Rows, attested, Attested),
|
|
1290
|
+
claim_count_sum(Rows, tested, Tested),
|
|
1291
|
+
claim_count_sum(Rows, unresolved, Unresolved).
|
|
1292
|
+
|
|
1293
|
+
claim_count_sum(Rows, Key, Sum) :-
|
|
1294
|
+
aggregate_all(sum(N),
|
|
1295
|
+
( member(Row, Rows),
|
|
1296
|
+
get_dict(claimSummary, Row, Summary),
|
|
1297
|
+
get_dict(Key, Summary, N),
|
|
1298
|
+
number(N)
|
|
1299
|
+
),
|
|
1300
|
+
Sum).
|
|
1301
|
+
|
|
1302
|
+
% implements REQ-kibi-evidence-floors
|
|
1303
|
+
% Current requirements with a floor (required_evidence, or tested for a firm
|
|
1304
|
+
% requirement), by whether their evidence level reaches it. A floor of
|
|
1305
|
+
% unverified accepts anything and is not counted.
|
|
1306
|
+
evidence_floor_counts(Rows, _{met: Met, below: Below, pendingReproof: Pending}) :-
|
|
1307
|
+
evidence_floor_status_count(Rows, met, Met),
|
|
1308
|
+
evidence_floor_status_count(Rows, below, Below),
|
|
1309
|
+
evidence_floor_status_count(Rows, pending_reproof, Pending).
|
|
1310
|
+
|
|
1311
|
+
evidence_floor_status_count(Rows, Status, Count) :-
|
|
1312
|
+
aggregate_all(count,
|
|
1313
|
+
( member(Row, Rows),
|
|
1314
|
+
get_dict(evidenceFloor, Row, Floor),
|
|
1315
|
+
Floor.get(required) \== unverified,
|
|
1316
|
+
Floor.get(status) == Status
|
|
1317
|
+
),
|
|
1318
|
+
Count).
|
|
1319
|
+
|
|
1162
1320
|
% implements REQ-kibi-evidence-levels
|
|
1163
1321
|
evidence_level_counts(Rows, _{proved: Proved, tested: Tested, asserted: Asserted, unverified: Unverified}) :-
|
|
1164
1322
|
aggregate_all(count, (member(Row, Rows), Row.get(evidenceLevel) == proved), Proved),
|
package/src/intervals.pl
CHANGED
|
@@ -30,6 +30,7 @@ numeric_constraints_satisfiable(Constraints) :-
|
|
|
30
30
|
% integer lies strictly between 0 and 1, and eq 0.5 has no integer solution.
|
|
31
31
|
% IntegerVariables may also be the atom `all`, making every variable integral.
|
|
32
32
|
numeric_constraints_satisfiable(Constraints, IntegerVariables) :-
|
|
33
|
+
maplist(finite_constraint, Constraints),
|
|
33
34
|
term_variables(Constraints, Variables),
|
|
34
35
|
forall(
|
|
35
36
|
member(Variable, Variables),
|
|
@@ -49,6 +50,16 @@ numeric_constraint_entailed(Premises, Constraint, IntegerVariables) :-
|
|
|
49
50
|
numeric_constraint_negation(Constraint, Negated),
|
|
50
51
|
\+ numeric_constraints_satisfiable([Negated|Premises], IntegerVariables).
|
|
51
52
|
|
|
53
|
+
% A NaN or infinite bound has no place on the number line the intervals
|
|
54
|
+
% model, so it is an error rather than an answer.
|
|
55
|
+
finite_constraint(c(_Op, _Variable, Value)) :-
|
|
56
|
+
( float(Value),
|
|
57
|
+
float_class(Value, Class),
|
|
58
|
+
memberchk(Class, [nan, infinite])
|
|
59
|
+
-> domain_error(finite_number, Value)
|
|
60
|
+
; true
|
|
61
|
+
).
|
|
62
|
+
|
|
52
63
|
integer_variable(_, all) :- !.
|
|
53
64
|
integer_variable(Variable, IntegerVariables) :-
|
|
54
65
|
is_list(IntegerVariables),
|
package/src/kb.pl
CHANGED
|
@@ -3121,6 +3121,13 @@ value_from_props(Props, string, Value) :- memberchk(value_string=ValueRaw, Props
|
|
|
3121
3121
|
value_from_props(Props, int, Value) :- memberchk(value_int=ValueRaw, Props), !, unwrap_rdf_value(ValueRaw, Value).
|
|
3122
3122
|
value_from_props(Props, number, Value) :- memberchk(value_number=ValueRaw, Props), !, unwrap_rdf_value(ValueRaw, Value).
|
|
3123
3123
|
value_from_props(Props, bool, Value) :- memberchk(value_bool=ValueRaw, Props), !, unwrap_rdf_value(ValueRaw, Value).
|
|
3124
|
+
% A set value is its sorted, unique members, so two facts naming the same set
|
|
3125
|
+
% in any order compare equal.
|
|
3126
|
+
value_from_props(Props, set, Value) :-
|
|
3127
|
+
memberchk(value_set=ValueRaw, Props),
|
|
3128
|
+
normalize_term_atom_list(ValueRaw, Atoms),
|
|
3129
|
+
!,
|
|
3130
|
+
sort(Atoms, Value).
|
|
3124
3131
|
value_from_props(_, _, '').
|
|
3125
3132
|
|
|
3126
3133
|
%% unwrap_rdf_value(+Raw, -Value)
|