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.
@@ -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)