kibi-core 0.26.1 → 0.28.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/src/discovery.pl CHANGED
@@ -9,6 +9,7 @@
9
9
  coverage_report_json/12,
10
10
  coverage_report_json/13,
11
11
  coverage_evidence_json/5,
12
+ coverage_evidence_json/7,
12
13
  evidence_floor_json/6,
13
14
  requirement_proof_json/5,
14
15
  requirement_proof_json/7,
@@ -155,12 +156,14 @@ symbol_explain_role(_SymbolId, unknown).
155
156
  % not used: they take seconds on a large KB. The functional-predicate
156
157
  % declarations a rule comparison reads are collected once per call rather
157
158
  % than once per compared pair, which dominated the cost for requirements with
158
- % many rules. A requirement whose checks raise an error reports that error
159
- % instead of failing the answer. Scope names the snapshot the verdicts were
160
- % computed from.
159
+ % many rules; the infeasible success scenarios are likewise analysed once per
160
+ % call rather than once per requirement. A requirement whose checks raise an
161
+ % error reports that error instead of failing the answer. Scope names the
162
+ % snapshot the verdicts were computed from.
161
163
  search_answer_verdicts_json(ReqIds, JsonString) :-
162
164
  search_answer_functional(Functional),
163
- maplist(search_answer_verdict(Functional), ReqIds, Verdicts),
165
+ search_answer_infeasible(Infeasible),
166
+ maplist(search_answer_verdict(Functional, Infeasible), ReqIds, Verdicts),
164
167
  status_scope_dict(Scope),
165
168
  dict_json_string(_{requirements: Verdicts, scope: Scope}, JsonString).
166
169
 
@@ -171,14 +174,27 @@ search_answer_functional(some(Functional)) :-
171
174
  !.
172
175
  search_answer_functional(none).
173
176
 
177
+ % Every infeasible success scenario, analysed once per call: the analysis
178
+ % walks every success scenario, so running it per requirement multiplied a
179
+ % whole-KB pass by the number of requirements in the answer. When it raises,
180
+ % each requirement's forbids check raises the same error itself and reports
181
+ % it as that requirement's error, as before.
182
+ search_answer_infeasible(some(Infeasible)) :-
183
+ catch(findall(infeasible(ScenarioId, ReqIds, AssumedFacts, ReqFacts, Reason),
184
+ infeasible_scenario(ScenarioId, ReqIds, AssumedFacts, ReqFacts, Reason),
185
+ Infeasible),
186
+ _, fail),
187
+ !.
188
+ search_answer_infeasible(none).
189
+
174
190
  search_answer_rule_status(some(Functional), RuleA, RuleB, Status) :-
175
191
  !,
176
192
  logic_rule_conflict(RuleA, RuleB, Functional, Status).
177
193
  search_answer_rule_status(none, RuleA, RuleB, Status) :-
178
194
  logic_rule_conflict(RuleA, RuleB, Status).
179
195
 
180
- search_answer_verdict(Functional, ReqId, Verdict) :-
181
- catch(search_answer_verdict_(Functional, ReqId, Verdict0), Error, true),
196
+ search_answer_verdict(Functional, Infeasible, ReqId, Verdict) :-
197
+ catch(search_answer_verdict_(Functional, Infeasible, ReqId, Verdict0), Error, true),
182
198
  ( var(Error)
183
199
  -> Verdict = Verdict0
184
200
  ; format(string(Text0), "~q", [Error]),
@@ -186,7 +202,7 @@ search_answer_verdict(Functional, ReqId, Verdict) :-
186
202
  Verdict = _{id: ReqId, error: Text}
187
203
  ).
188
204
 
189
- search_answer_verdict_(Functional, ReqId, _{
205
+ search_answer_verdict_(Functional, Infeasible, ReqId, _{
190
206
  id: ReqId,
191
207
  contradictions: Contradictions,
192
208
  scenarios: Scenarios,
@@ -197,7 +213,7 @@ search_answer_verdict_(Functional, ReqId, _{
197
213
  sort(Contradictions0, Contradictions),
198
214
  findall(S, search_answer_scenario_outcome(ReqId, S), Scenarios0),
199
215
  sort(Scenarios0, Scenarios),
200
- findall(F, search_answer_forbidden_scenario(ReqId, F), Forbids0),
216
+ findall(F, search_answer_forbidden_scenario(Infeasible, ReqId, F), Forbids0),
201
217
  sort(Forbids0, Forbids),
202
218
  search_answer_inventory(ReqId, Inventory).
203
219
 
@@ -291,7 +307,18 @@ search_answer_outcome(ScenarioId, Result, _{scenario: ScenarioId, outcome: Outco
291
307
  ; format(atom(Outcome), "~q", [Result])
292
308
  ).
293
309
 
294
- search_answer_forbidden_scenario(ReqId, _{
310
+ search_answer_forbidden_scenario(some(Infeasible), ReqId, _{
311
+ scenario: ScenarioId,
312
+ assumed: AssumedFacts,
313
+ fact: ReqFacts,
314
+ reason: Reason
315
+ }) :-
316
+ member(infeasible(ScenarioId, ReqIds, AssumedFacts, ReqFacts, RawReason), Infeasible),
317
+ memberchk(ReqId, ReqIds),
318
+ \+ kb_relationship(specified_by, ReqId, ScenarioId),
319
+ search_answer_text(RawReason, Reason).
320
+
321
+ search_answer_forbidden_scenario(none, ReqId, _{
295
322
  scenario: ScenarioId,
296
323
  assumed: AssumedFacts,
297
324
  fact: ReqFacts,
@@ -352,6 +379,16 @@ search_answer_clip(Text, Max, Clipped) :-
352
379
  % the full coverage row set, keeping the response small on large KBs.
353
380
  coverage_evidence_json(Tags, VerificationSnapshot, CheckedAt, MaxAgeSeconds, JsonString) :-
354
381
  requirement_proof_context(VerificationSnapshot, CheckedAt, MaxAgeSeconds, ProofContext),
382
+ coverage_evidence_rows_json(Tags, ProofContext, JsonString).
383
+
384
+ % The same rows under a receipt binding mode, so kb_check judges receipts
385
+ % like kb_coverage and kibi proof explain: per_contract with the TestId ->
386
+ % binding hash dict, or strict_snapshot.
387
+ coverage_evidence_json(Tags, BindingMode, TestBindings, VerificationSnapshot, CheckedAt, MaxAgeSeconds, JsonString) :-
388
+ requirement_proof_context(VerificationSnapshot, CheckedAt, MaxAgeSeconds, BindingMode, TestBindings, ProofContext),
389
+ coverage_evidence_rows_json(Tags, ProofContext, JsonString).
390
+
391
+ coverage_evidence_rows_json(Tags, ProofContext, JsonString) :-
355
392
  findall(Evidence,
356
393
  ( kb_entity(Id, req, Props),
357
394
  matches_tags(Tags, Props),
@@ -479,9 +516,9 @@ coverage_report_json(By, Tags, IncludePassing, BindingMode, TestBindings, Includ
479
516
  binding(By, Tags, IncludePassing, BindingMode, TestBindings, IncludeTransitive, VerificationSnapshot, CheckedAt, MaxAgeSeconds),
480
517
  binding_coverage_rows(By, Tags, IncludePassing, BindingMode, TestBindings, IncludeTransitive, VerificationSnapshot, CheckedAt, MaxAgeSeconds),
481
518
  SortedRows,
482
- Summary),
519
+ Summary,
520
+ Meta),
483
521
  paginate_rows(SortedRows, Offset, Limit, Rows),
484
- status_meta_dict(Meta),
485
522
  Response = _{summary: Summary, rows: Rows, meta: Meta},
486
523
  dict_json_string(Response, JsonString).
487
524
 
@@ -497,9 +534,9 @@ coverage_report_json(By, Tags, IncludePassing, StatusFilter, IncludeTransitive,
497
534
  status(By, Tags, IncludePassing, StatusFilter, IncludeTransitive, VerificationSnapshot, CheckedAt, MaxAgeSeconds),
498
535
  status_filtered_coverage_rows(By, Tags, IncludePassing, StatusFilter, IncludeTransitive, VerificationSnapshot, CheckedAt, MaxAgeSeconds),
499
536
  SortedRows,
500
- Summary),
537
+ Summary,
538
+ Meta),
501
539
  paginate_rows(SortedRows, Offset, Limit, Rows),
502
- status_meta_dict(Meta),
503
540
  Response = _{summary: Summary, rows: Rows, meta: Meta},
504
541
  dict_json_string(Response, JsonString).
505
542
 
@@ -512,9 +549,9 @@ coverage_report_json(By, Tags, IncludePassing, StatusFilter, BindingMode, TestBi
512
549
  status_binding(By, Tags, IncludePassing, StatusFilter, BindingMode, TestBindings, IncludeTransitive, VerificationSnapshot, CheckedAt, MaxAgeSeconds),
513
550
  status_binding_coverage_rows(By, Tags, IncludePassing, StatusFilter, BindingMode, TestBindings, IncludeTransitive, VerificationSnapshot, CheckedAt, MaxAgeSeconds),
514
551
  SortedRows,
515
- Summary),
552
+ Summary,
553
+ Meta),
516
554
  paginate_rows(SortedRows, Offset, Limit, Rows),
517
- status_meta_dict(Meta),
518
555
  Response = _{summary: Summary, rows: Rows, meta: Meta},
519
556
  dict_json_string(Response, JsonString).
520
557
 
@@ -531,26 +568,31 @@ binding_coverage_rows(req, Tags, IncludePassing, BindingMode, TestBindings, Incl
531
568
  binding_coverage_rows(By, Tags, IncludePassing, _BindingMode, _TestBindings, IncludeTransitive, VerificationSnapshot, CheckedAt, MaxAgeSeconds, Rows, Summary) :-
532
569
  coverage_rows(By, Tags, IncludePassing, IncludeTransitive, VerificationSnapshot, CheckedAt, MaxAgeSeconds, Rows, Summary).
533
570
 
534
- :- dynamic coverage_memo/3.
571
+ :- dynamic coverage_memo/4.
535
572
 
536
- %% memo_sorted_coverage(+Args, :Compute, -SortedRows, -Summary)
573
+ %% memo_sorted_coverage(+Args, :Compute, -SortedRows, -Summary, -Meta)
537
574
  % Large coverage reports are fetched in bounded pages so no single answer
538
575
  % approaches the engine's output cap. Every page would otherwise recompute
539
576
  % the whole report; keep the most recent sorted result for the exact
540
577
  % arguments, graph, and RDF generation. Coverage reads only the store and its
541
- % arguments, so the memo is invalidated by any store change.
542
- memo_sorted_coverage(Args, Compute, SortedRows, Summary) :-
578
+ % arguments, so the memo is invalidated by any store change. The status meta
579
+ % is kept with it: its freshness check stats every indexed source, which cost
580
+ % more than the page itself, and the pages of one report share its arguments
581
+ % (including the check time), so they describe the same snapshot.
582
+ memo_sorted_coverage(Args, Compute, SortedRows, Summary, Meta) :-
543
583
  (kb:kb_graph(Graph) -> true ; Graph = none),
544
584
  rdf_generation(Generation),
545
585
  Key = key(Args, Graph, Generation),
546
- ( coverage_memo(Stored, StoredRows, StoredSummary),
586
+ ( coverage_memo(Stored, StoredRows, StoredSummary, StoredMeta),
547
587
  Stored =@= Key
548
588
  -> SortedRows = StoredRows,
549
- Summary = StoredSummary
589
+ Summary = StoredSummary,
590
+ Meta = StoredMeta
550
591
  ; call(Compute, Rows0, Summary),
551
592
  sort_dict_rows(Rows0, SortedRows),
552
- retractall(coverage_memo(_, _, _)),
553
- assertz(coverage_memo(Key, SortedRows, Summary))
593
+ status_meta_dict(Meta),
594
+ retractall(coverage_memo(_, _, _, _)),
595
+ assertz(coverage_memo(Key, SortedRows, Summary, Meta))
554
596
  ).
555
597
 
556
598
  status_filtered_coverage_rows(By, Tags, IncludePassing, [], IncludeTransitive, VerificationSnapshot, CheckedAt, MaxAgeSeconds, Rows, Summary) :-
package/src/intervals.pl CHANGED
@@ -22,6 +22,7 @@
22
22
  %% numeric_constraints_satisfiable(+Constraints)
23
23
  % True when some assignment of reals satisfies every constraint.
24
24
  numeric_constraints_satisfiable(Constraints) :-
25
+ % mutalog-disable-next-line literal: [_] names no variable, so it equals []
25
26
  numeric_constraints_satisfiable(Constraints, []).
26
27
 
27
28
  %% numeric_constraints_satisfiable(+Constraints, +IntegerVariables)
@@ -43,6 +44,7 @@ numeric_constraints_satisfiable(Constraints, IntegerVariables) :-
43
44
  %% numeric_constraint_entailed(+Premises, +Constraint)
44
45
  % True when every assignment that satisfies Premises satisfies Constraint.
45
46
  numeric_constraint_entailed(Premises, Constraint) :-
47
+ % mutalog-disable-next-line literal: [_] names no variable, so it equals []
46
48
  numeric_constraint_entailed(Premises, Constraint, []).
47
49
 
48
50
  %% numeric_constraint_entailed(+Premises, +Constraint, +IntegerVariables)
@@ -60,12 +62,12 @@ finite_constraint(c(_Op, _Variable, Value)) :-
60
62
  ; true
61
63
  ).
62
64
 
63
- integer_variable(_, all) :- !.
65
+ % Only called as an if-then-else condition, which keeps the first answer.
66
+ integer_variable(_, all).
64
67
  integer_variable(Variable, IntegerVariables) :-
65
68
  is_list(IntegerVariables),
66
69
  member(Candidate, IntegerVariables),
67
- Candidate == Variable,
68
- !.
70
+ Candidate == Variable.
69
71
 
70
72
  %% numeric_constraint_negation(+Constraint, -Negated)
71
73
  numeric_constraint_negation(c(Op, Variable, Value), c(Negated, Variable, Value)) :-
@@ -80,10 +82,15 @@ negated_operator(gt, lte).
80
82
 
81
83
  %% numeric_constraint_holds(+Op, +Left, +Right)
82
84
  % Evaluate a comparison between two numbers.
85
+ % mutalog-disable-next-line cut_insert: a comparison has one answer and the operators are distinct
83
86
  numeric_constraint_holds(eq, Left, Right) :- Left =:= Right.
87
+ % mutalog-disable-next-line cut_insert: a comparison has one answer and the operators are distinct
84
88
  numeric_constraint_holds(neq, Left, Right) :- Left =\= Right.
89
+ % mutalog-disable-next-line cut_insert: a comparison has one answer and the operators are distinct
85
90
  numeric_constraint_holds(lt, Left, Right) :- Left < Right.
91
+ % mutalog-disable-next-line cut_insert: a comparison has one answer and the operators are distinct
86
92
  numeric_constraint_holds(lte, Left, Right) :- Left =< Right.
93
+ % mutalog-disable-next-line cut_insert: a comparison has one answer and the operators are distinct
87
94
  numeric_constraint_holds(gt, Left, Right) :- Left > Right.
88
95
  numeric_constraint_holds(gte, Left, Right) :- Left >= Right.
89
96
 
@@ -94,14 +101,18 @@ variable_feasible(Variable, Constraints) :-
94
101
 
95
102
  % interval(Low, LowKind, High, HighKind, Excluded)
96
103
  apply_bound(eq-Value, interval(Lo, LoK, Hi, HiK, Ex), interval(Lo1, LoK1, Hi1, HiK1, Ex)) :-
104
+ % mutalog-disable-next-line cut_insert: raise_low/6 and lower_high/6 are det and the operators are distinct
97
105
  raise_low(Lo, LoK, Value, closed, Lo1, LoK1),
98
106
  lower_high(Hi, HiK, Value, closed, Hi1, HiK1).
99
107
  apply_bound(neq-Value, interval(Lo, LoK, Hi, HiK, Ex), interval(Lo, LoK, Hi, HiK, [Value|Ex])).
100
108
  apply_bound(lt-Value, interval(Lo, LoK, Hi, HiK, Ex), interval(Lo, LoK, Hi1, HiK1, Ex)) :-
109
+ % mutalog-disable-next-line cut_insert: raise_low/6 and lower_high/6 are det and the operators are distinct
101
110
  lower_high(Hi, HiK, Value, open, Hi1, HiK1).
102
111
  apply_bound(lte-Value, interval(Lo, LoK, Hi, HiK, Ex), interval(Lo, LoK, Hi1, HiK1, Ex)) :-
112
+ % mutalog-disable-next-line cut_insert: raise_low/6 and lower_high/6 are det and the operators are distinct
103
113
  lower_high(Hi, HiK, Value, closed, Hi1, HiK1).
104
114
  apply_bound(gt-Value, interval(Lo, LoK, Hi, HiK, Ex), interval(Lo1, LoK1, Hi, HiK, Ex)) :-
115
+ % mutalog-disable-next-line cut_insert: raise_low/6 and lower_high/6 are det and the operators are distinct
105
116
  raise_low(Lo, LoK, Value, open, Lo1, LoK1).
106
117
  apply_bound(gte-Value, interval(Lo, LoK, Hi, HiK, Ex), interval(Lo1, LoK1, Hi, HiK, Ex)) :-
107
118
  raise_low(Lo, LoK, Value, closed, Lo1, LoK1).
@@ -116,13 +127,18 @@ lower_high(Hi, _, Value, Kind, Value, Kind) :- Value < Hi, !.
116
127
  lower_high(Hi, HiK, Value, Kind, Hi, Kind1) :- Value =:= Hi, !, tighter_kind(HiK, Kind, Kind1).
117
128
  lower_high(Hi, HiK, _, _, Hi, HiK).
118
129
 
130
+ % mutalog-disable-next-line cut: the second answer, open, only shrinks the interval
119
131
  tighter_kind(closed, closed, closed) :- !.
120
132
  tighter_kind(_, _, open).
121
133
 
122
134
  % An unbounded or non-degenerate real interval cannot be emptied by finitely
123
135
  % many excluded points. A degenerate interval is a single closed point.
136
+ % The callers keep only the first answer, so the cuts are not observable.
137
+ % mutalog-disable-next-line cut
124
138
  interval_non_empty(interval(ninf, _, _, _, _)) :- !.
139
+ % mutalog-disable-next-line cut
125
140
  interval_non_empty(interval(_, _, pinf, _, _)) :- !.
141
+ % mutalog-disable-next-line cut
126
142
  interval_non_empty(interval(Lo, _, Hi, _, _)) :- Lo < Hi, !.
127
143
  interval_non_empty(interval(Lo, closed, Hi, closed, Excluded)) :-
128
144
  Lo =:= Hi,
@@ -131,34 +147,37 @@ interval_non_empty(interval(Lo, closed, Hi, closed, Excluded)) :-
131
147
  % Integer variables: every strict or fractional bound is tightened to the
132
148
  % nearest admissible integer, so the set is an integer range [Lo, Hi] minus
133
149
  % finitely many excluded integers. An eq bound with a fractional value admits
134
- % no integer at all.
150
+ % no integer at all, so the fold fails. The operators are distinct, so the
151
+ % cuts after the heads are not observable.
135
152
  integer_variable_feasible(Variable, Constraints) :-
136
153
  findall(Op-Value, (member(c(Op, Other, Value), Constraints), Other == Variable), Bounds),
137
154
  foldl(apply_integer_bound, Bounds, ibox(ninf, pinf, []), Box),
138
155
  integer_box_non_empty(Box).
139
156
 
140
- apply_integer_bound(_, empty, empty) :- !.
141
- apply_integer_bound(eq-Value, ibox(Lo, Hi, Ex), Box) :- !,
142
- ( integral_value(Value, Integer)
143
- -> integer_raise_low(Lo, Integer, Lo1),
144
- integer_lower_high(Hi, Integer, Hi1),
145
- Box = ibox(Lo1, Hi1, Ex)
146
- ; Box = empty
147
- ).
157
+ % mutalog-disable-next-line cut
158
+ apply_integer_bound(eq-Value, ibox(Lo, Hi, Ex), ibox(Lo1, Hi1, Ex)) :- !,
159
+ integral_value(Value, Integer),
160
+ integer_raise_low(Lo, Integer, Lo1),
161
+ integer_lower_high(Hi, Integer, Hi1).
162
+ % mutalog-disable-next-line cut
148
163
  apply_integer_bound(neq-Value, ibox(Lo, Hi, Ex), ibox(Lo, Hi, Ex1)) :- !,
149
164
  ( integral_value(Value, Integer)
150
165
  -> Ex1 = [Integer|Ex]
151
166
  ; Ex1 = Ex
152
167
  ).
168
+ % mutalog-disable-next-line cut
153
169
  apply_integer_bound(gt-Value, ibox(Lo, Hi, Ex), ibox(Lo1, Hi, Ex)) :- !,
154
170
  Bound is floor(Value) + 1,
155
171
  integer_raise_low(Lo, Bound, Lo1).
172
+ % mutalog-disable-next-line cut
156
173
  apply_integer_bound(gte-Value, ibox(Lo, Hi, Ex), ibox(Lo1, Hi, Ex)) :- !,
157
174
  Bound is ceiling(Value),
158
175
  integer_raise_low(Lo, Bound, Lo1).
176
+ % mutalog-disable-next-line cut
159
177
  apply_integer_bound(lt-Value, ibox(Lo, Hi, Ex), ibox(Lo, Hi1, Ex)) :- !,
160
178
  Bound is ceiling(Value) - 1,
161
179
  integer_lower_high(Hi, Bound, Hi1).
180
+ % mutalog-disable-next-line cut
162
181
  apply_integer_bound(lte-Value, ibox(Lo, Hi, Ex), ibox(Lo, Hi1, Ex)) :- !,
163
182
  Bound is floor(Value),
164
183
  integer_lower_high(Hi, Bound, Hi1).
@@ -175,11 +194,13 @@ integer_lower_high(pinf, Bound, Bound) :- !.
175
194
  integer_lower_high(Hi, Bound, Hi1) :- Hi1 is min(Hi, Bound).
176
195
 
177
196
  % An unbounded integer range cannot be emptied by finitely many exclusions.
178
- integer_box_non_empty(empty) :- !, fail.
197
+ % A range with Lo above Hi has a non-positive size, so it fails the count.
198
+ % The callers keep only the first answer, so the cuts are not observable.
199
+ % mutalog-disable-next-line cut
179
200
  integer_box_non_empty(ibox(ninf, _, _)) :- !.
201
+ % mutalog-disable-next-line cut
180
202
  integer_box_non_empty(ibox(_, pinf, _)) :- !.
181
203
  integer_box_non_empty(ibox(Lo, Hi, Excluded)) :-
182
- Lo =< Hi,
183
204
  findall(Value, (member(Value, Excluded), Value >= Lo, Value =< Hi), Inside0),
184
205
  sort(Inside0, Inside),
185
206
  length(Inside, ExcludedCount),