kibi-core 0.22.5 → 0.23.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/src/certificate_check.pl +756 -0
- package/src/certificates.pl +237 -0
- package/src/checks.pl +19 -4
- package/src/discovery.pl +17 -8
- package/src/kb_consistency.pl +213 -0
- package/src/requirement_proof.pl +90 -7
- package/src/rule_registry.pl +12 -0
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
% Module: certificates
|
|
2
|
+
% implements REQ-kibi-kb-certificates
|
|
3
|
+
%
|
|
4
|
+
% Finds a certificate for every KB theorem that certificate_check.pl states:
|
|
5
|
+
% a model for each consistent property context, predicate atom and decision
|
|
6
|
+
% key, an irreducible core (with a model per dropped fact) for each
|
|
7
|
+
% contradiction, and a stratum assignment (or a negation cycle) for the
|
|
8
|
+
% stored rules. This module may search; certificate_check.pl re-checks
|
|
9
|
+
% everything it emits without trusting it.
|
|
10
|
+
|
|
11
|
+
:- module(certificates, [
|
|
12
|
+
kb_certificates/1,
|
|
13
|
+
kb_write_certificates/2
|
|
14
|
+
]).
|
|
15
|
+
|
|
16
|
+
:- encoding(utf8).
|
|
17
|
+
|
|
18
|
+
:- use_module(library(http/json)).
|
|
19
|
+
:- use_module(library(apply)).
|
|
20
|
+
:- use_module(library(lists)).
|
|
21
|
+
:- use_module(library(aggregate)).
|
|
22
|
+
:- use_module('kb.pl', []).
|
|
23
|
+
:- use_module('intervals.pl', [numeric_constraint_holds/3]).
|
|
24
|
+
:- use_module('certificate_check.pl', [
|
|
25
|
+
certificate_property_contexts/1,
|
|
26
|
+
certificate_predicate_groups/1,
|
|
27
|
+
certificate_decision_groups/1,
|
|
28
|
+
certificate_rule_edges/1,
|
|
29
|
+
certificate_member_holds/3,
|
|
30
|
+
certificate_members_satisfiable/2
|
|
31
|
+
]).
|
|
32
|
+
|
|
33
|
+
%% kb_write_certificates(+Path, -JsonString)
|
|
34
|
+
% Write the certificate bundle for the current KB to Path and return a
|
|
35
|
+
% summary (certificate count per kind) as a JSON string.
|
|
36
|
+
kb_write_certificates(Path, JsonString) :-
|
|
37
|
+
kb_certificates(Bundle),
|
|
38
|
+
setup_call_cleanup(
|
|
39
|
+
open(Path, write, Stream, [encoding(utf8)]),
|
|
40
|
+
( json_write_dict(Stream, Bundle, [width(0)]), nl(Stream) ),
|
|
41
|
+
close(Stream)),
|
|
42
|
+
findall(Kind, (member(Certificate, Bundle.certificates), get_dict(kind, Certificate, Kind)), Kinds0),
|
|
43
|
+
msort(Kinds0, Kinds),
|
|
44
|
+
clumped(Kinds, Counts),
|
|
45
|
+
findall(Kind-Count, member(Kind-Count, Counts), Pairs),
|
|
46
|
+
dict_pairs(ByKind, _, Pairs),
|
|
47
|
+
length(Bundle.certificates, Total),
|
|
48
|
+
with_output_to(string(JsonString),
|
|
49
|
+
json_write_dict(current_output, _{path: Path, certificates: Total, byKind: ByKind}, [width(0)])).
|
|
50
|
+
|
|
51
|
+
%% kb_certificates(-Bundle)
|
|
52
|
+
kb_certificates(_{version: 'kibi.certificates.v1', certificates: Certificates}) :-
|
|
53
|
+
certificate_property_contexts(Contexts),
|
|
54
|
+
convlist(property_certificate, Contexts, PropertyCertificates),
|
|
55
|
+
certificate_predicate_groups(AtomGroups),
|
|
56
|
+
convlist(group_certificate(predicate), AtomGroups, PredicateCertificates),
|
|
57
|
+
certificate_decision_groups(DecisionGroups),
|
|
58
|
+
convlist(group_certificate(decision), DecisionGroups, DecisionCertificates),
|
|
59
|
+
certificate_rule_edges(Edges),
|
|
60
|
+
rule_certificates(Edges, RuleCertificates),
|
|
61
|
+
append([PropertyCertificates, PredicateCertificates, DecisionCertificates, RuleCertificates], Certificates).
|
|
62
|
+
|
|
63
|
+
% ------------------------------------------------------------------
|
|
64
|
+
% Property contexts
|
|
65
|
+
% ------------------------------------------------------------------
|
|
66
|
+
|
|
67
|
+
property_certificate(context(Subject, Property, Scope, Domain, Members), Certificate) :-
|
|
68
|
+
Base = _{subjectKey: Subject, propertyKey: Property, scope: Scope, domain: Domain},
|
|
69
|
+
( members_model(Domain, Members, Value)
|
|
70
|
+
-> findall(Fact, member(m(Fact, _, _, _, _), Members), Facts),
|
|
71
|
+
Certificate = Base.put(_{kind: property_model, value: Value, facts: Facts})
|
|
72
|
+
; irreducible_core(Domain, Members, Core),
|
|
73
|
+
findall(_{factId: Fact, value: Value},
|
|
74
|
+
( member(Dropped, Core),
|
|
75
|
+
Dropped = m(Fact, _, _, _, _),
|
|
76
|
+
exclude(==(Dropped), Core, Rest),
|
|
77
|
+
members_model(Domain, Rest, Value)
|
|
78
|
+
),
|
|
79
|
+
DropModels),
|
|
80
|
+
findall(Fact, member(m(Fact, _, _, _, _), Core), CoreFacts),
|
|
81
|
+
( core_windows_overlap(Core)
|
|
82
|
+
-> Kind = property_contradiction
|
|
83
|
+
; Kind = property_unresolved
|
|
84
|
+
),
|
|
85
|
+
Certificate = Base.put(_{kind: Kind, core: CoreFacts, dropModels: DropModels})
|
|
86
|
+
).
|
|
87
|
+
|
|
88
|
+
%% members_model(+Domain, +Members, -Value)
|
|
89
|
+
% A value satisfying every member, found among finitely many candidates that
|
|
90
|
+
% meet every region the members' bounds cut the line into.
|
|
91
|
+
members_model(Domain, Members, Value) :-
|
|
92
|
+
model_candidates(Domain, Members, Candidates),
|
|
93
|
+
member(Value, Candidates),
|
|
94
|
+
forall(member(Member, Members), certificate_member_holds(Domain, Member, Value)),
|
|
95
|
+
!.
|
|
96
|
+
|
|
97
|
+
model_candidates(Domain, Members, Candidates) :-
|
|
98
|
+
memberchk(Domain, [int, number]),
|
|
99
|
+
!,
|
|
100
|
+
findall(Bound, member(m(_, _, Bound, _, _), Members), Bounds0),
|
|
101
|
+
sort(Bounds0, Bounds),
|
|
102
|
+
( Domain == int
|
|
103
|
+
-> findall(Candidate,
|
|
104
|
+
( member(Bound, Bounds),
|
|
105
|
+
member(Base, [floor(Bound), ceiling(Bound)]),
|
|
106
|
+
member(Offset, [-1, 0, 1]),
|
|
107
|
+
Candidate is Base + Offset
|
|
108
|
+
),
|
|
109
|
+
Candidates0)
|
|
110
|
+
; findall(Candidate,
|
|
111
|
+
( member(Bound, Bounds), member(Offset, [-1, 0, 1]), Candidate is Bound + Offset
|
|
112
|
+
; nextto(Low, High, Bounds), Candidate is (Low + High) / 2
|
|
113
|
+
),
|
|
114
|
+
Candidates0)
|
|
115
|
+
),
|
|
116
|
+
sort([0|Candidates0], Candidates).
|
|
117
|
+
model_candidates(bool, _Members, [true, false]) :- !.
|
|
118
|
+
model_candidates(string, Members, Candidates) :-
|
|
119
|
+
findall(Bound, member(m(_, _, Bound, _, _), Members), Bounds0),
|
|
120
|
+
sort(Bounds0, Bounds),
|
|
121
|
+
fresh_text(Bounds, 0, Fresh),
|
|
122
|
+
append(Bounds, [Fresh], Candidates).
|
|
123
|
+
|
|
124
|
+
fresh_text(Bounds, Index, Fresh) :-
|
|
125
|
+
format(string(Candidate), "kibi:model:~w", [Index]),
|
|
126
|
+
( member(Bound, Bounds), text_value(Bound, Candidate)
|
|
127
|
+
-> Next is Index + 1,
|
|
128
|
+
fresh_text(Bounds, Next, Fresh)
|
|
129
|
+
; Fresh = Candidate
|
|
130
|
+
).
|
|
131
|
+
|
|
132
|
+
text_value(Value, Text) :- atom(Value), !, atom_string(Value, Text).
|
|
133
|
+
text_value(Value, Text) :- string(Value), !, Text = Value.
|
|
134
|
+
|
|
135
|
+
%% irreducible_core(+Domain, +Members, -Core)
|
|
136
|
+
% Deletion filter: drop each member whose removal keeps the set
|
|
137
|
+
% unsatisfiable. What remains admits no value, and every proper subset
|
|
138
|
+
% does.
|
|
139
|
+
irreducible_core(Domain, Members, Core) :-
|
|
140
|
+
foldl(core_step(Domain), Members, Members, Core).
|
|
141
|
+
|
|
142
|
+
core_step(Domain, Member, Current, Next) :-
|
|
143
|
+
exclude(==(Member), Current, Without),
|
|
144
|
+
( Without \== [],
|
|
145
|
+
\+ certificate_members_satisfiable(Domain, Without)
|
|
146
|
+
-> Next = Without
|
|
147
|
+
; Next = Current
|
|
148
|
+
).
|
|
149
|
+
|
|
150
|
+
core_windows_overlap(Core) :-
|
|
151
|
+
forall((member(m(_, _, _, FromA, ToA), Core), member(m(_, _, _, FromB, ToB), Core)),
|
|
152
|
+
kb:intervals_overlap(FromA, ToA, FromB, ToB)).
|
|
153
|
+
|
|
154
|
+
% ------------------------------------------------------------------
|
|
155
|
+
% Predicate atoms and decision keys
|
|
156
|
+
% ------------------------------------------------------------------
|
|
157
|
+
|
|
158
|
+
group_certificate(Prefix, group(Label, Members), Certificate) :-
|
|
159
|
+
atom_concat(Prefix, '_model', ModelKind),
|
|
160
|
+
atom_concat(Prefix, '_contradiction', ContradictionKind),
|
|
161
|
+
findall(Value, member(p(_, Value), Members), Values0),
|
|
162
|
+
sort(Values0, Values),
|
|
163
|
+
( Values = [Value]
|
|
164
|
+
-> findall(Fact, member(p(Fact, _), Members), Facts),
|
|
165
|
+
Certificate = Label.put(_{kind: ModelKind, value: Value, facts: Facts})
|
|
166
|
+
; Members = [p(FactA, ValueA)|_],
|
|
167
|
+
member(p(FactB, ValueB), Members),
|
|
168
|
+
ValueB \== ValueA,
|
|
169
|
+
!,
|
|
170
|
+
Certificate = Label.put(_{kind: ContradictionKind, core: [FactA, FactB]})
|
|
171
|
+
).
|
|
172
|
+
|
|
173
|
+
% ------------------------------------------------------------------
|
|
174
|
+
% Rule stratification
|
|
175
|
+
% ------------------------------------------------------------------
|
|
176
|
+
|
|
177
|
+
rule_certificates([], []) :- !.
|
|
178
|
+
rule_certificates(Edges, [Certificate]) :-
|
|
179
|
+
findall(Signature, (member(edge(Head, Body, _), Edges), member(Signature, [Head, Body])), Signatures0),
|
|
180
|
+
sort(Signatures0, Signatures),
|
|
181
|
+
findall(Signature-0, member(Signature, Signatures), Initial),
|
|
182
|
+
length(Signatures, Count),
|
|
183
|
+
Rounds is Count + 1,
|
|
184
|
+
( relax_strata(Rounds, Edges, Initial, Strata)
|
|
185
|
+
-> findall(_{namespace: Namespace, name: Name, arity: Arity, stratum: Stratum},
|
|
186
|
+
member(predicate(Namespace, Name, Arity)-Stratum, Strata),
|
|
187
|
+
Entries),
|
|
188
|
+
Certificate = _{kind: rule_strata, strata: Entries}
|
|
189
|
+
; negation_cycle(Edges, Cycle),
|
|
190
|
+
findall(_{head: HeadDict, body: BodyDict, sign: Sign},
|
|
191
|
+
( member(edge(Head, Body, Sign), Cycle),
|
|
192
|
+
signature_dict(Head, HeadDict),
|
|
193
|
+
signature_dict(Body, BodyDict)
|
|
194
|
+
),
|
|
195
|
+
Entries),
|
|
196
|
+
Certificate = _{kind: rule_negation_cycle, cycle: Entries}
|
|
197
|
+
).
|
|
198
|
+
|
|
199
|
+
signature_dict(predicate(Namespace, Name, Arity), _{namespace: Namespace, name: Name, arity: Arity}).
|
|
200
|
+
|
|
201
|
+
% Raise each head to at least its body's stratum (one more across a negated
|
|
202
|
+
% edge) until nothing changes. Without a negation cycle that happens within
|
|
203
|
+
% one round per predicate; a cycle keeps raising, and relaxation fails.
|
|
204
|
+
relax_strata(0, _Edges, _Strata, _Final) :- !, fail.
|
|
205
|
+
relax_strata(Rounds, Edges, Strata, Final) :-
|
|
206
|
+
foldl(relax_edge, Edges, Strata, Next),
|
|
207
|
+
( Next == Strata
|
|
208
|
+
-> Final = Strata
|
|
209
|
+
; Remaining is Rounds - 1,
|
|
210
|
+
relax_strata(Remaining, Edges, Next, Final)
|
|
211
|
+
).
|
|
212
|
+
|
|
213
|
+
relax_edge(edge(Head, Body, Sign), Strata, Next) :-
|
|
214
|
+
memberchk(Head-HeadStratum, Strata),
|
|
215
|
+
memberchk(Body-BodyStratum, Strata),
|
|
216
|
+
( Sign == negative -> Required is BodyStratum + 1 ; Required = BodyStratum ),
|
|
217
|
+
( HeadStratum < Required
|
|
218
|
+
-> selectchk(Head-HeadStratum, Strata, Head-Required, Next)
|
|
219
|
+
; Next = Strata
|
|
220
|
+
).
|
|
221
|
+
|
|
222
|
+
% A negated edge Head -> Body with a dependency path from Body back to Head.
|
|
223
|
+
negation_cycle(Edges, [edge(Head, Body, negative)|Path]) :-
|
|
224
|
+
member(edge(Head, Body, negative), Edges),
|
|
225
|
+
dependency_path(Body, Head, Edges, [Body], Path),
|
|
226
|
+
!.
|
|
227
|
+
|
|
228
|
+
dependency_path(Current, Target, _Edges, _Seen, []) :-
|
|
229
|
+
Current == Target,
|
|
230
|
+
!.
|
|
231
|
+
dependency_path(Current, Target, Edges, Seen, [edge(Current, Next, Sign)|Path]) :-
|
|
232
|
+
member(edge(Current, Next, Sign), Edges),
|
|
233
|
+
( Next == Target
|
|
234
|
+
-> Path = []
|
|
235
|
+
; \+ memberchk(Next, Seen),
|
|
236
|
+
dependency_path(Next, Target, Edges, [Next|Seen], Path)
|
|
237
|
+
).
|
package/src/checks.pl
CHANGED
|
@@ -50,6 +50,8 @@
|
|
|
50
50
|
check_subject_key_identity/1,
|
|
51
51
|
check_subject_key_shape/1,
|
|
52
52
|
check_ontology_quality/1,
|
|
53
|
+
check_kb_consistency/1,
|
|
54
|
+
check_kb_consistency_unresolved/1,
|
|
53
55
|
run_checks_json/0, % Entry point for JSON output
|
|
54
56
|
violation_id_text/2 % Extract text from entity ID term (exported for testing)
|
|
55
57
|
]).
|
|
@@ -75,6 +77,7 @@
|
|
|
75
77
|
:- use_module('logic_ir.pl', [logic_rule_safety/2, logic_rule_from_props/2, logic_rule_render/2, logic_rule_conflict/3, logic_rule_conflict_witness/4, logic_rules_stratified/1, logic_rule_missing_keys/4, functional_predicate_declarations/1, logic_rule_property_form/2]).
|
|
76
78
|
% GENERATED registry facts; single source is schema/rule-registry.json.
|
|
77
79
|
:- use_module('rule_registry.pl', [known_rule/1]).
|
|
80
|
+
:- use_module('kb_consistency.pl', [check_kb_consistency/1, check_kb_consistency_unresolved/1]).
|
|
78
81
|
|
|
79
82
|
% Required fields for all entities
|
|
80
83
|
required_fields([id, title, status, created_at, updated_at, source]).
|
|
@@ -125,6 +128,8 @@ check_all(ViolationsDict) :-
|
|
|
125
128
|
check_subject_key_identity(SubjectKeyIdentity),
|
|
126
129
|
check_subject_key_shape(SubjectKeyShape),
|
|
127
130
|
check_ontology_quality(OntologyQuality),
|
|
131
|
+
check_kb_consistency(KbConsistency),
|
|
132
|
+
check_kb_consistency_unresolved(KbConsistencyUnresolved),
|
|
128
133
|
ViolationsDict = _{
|
|
129
134
|
must_priority_coverage: MustPriority,
|
|
130
135
|
symbol_coverage: SymbolCoverage,
|
|
@@ -159,7 +164,9 @@ check_all(ViolationsDict) :-
|
|
|
159
164
|
domain_implication: DomainImplication,
|
|
160
165
|
subject_key_identity: SubjectKeyIdentity,
|
|
161
166
|
subject_key_shape: SubjectKeyShape,
|
|
162
|
-
ontology_quality: OntologyQuality
|
|
167
|
+
ontology_quality: OntologyQuality,
|
|
168
|
+
kb_consistency: KbConsistency,
|
|
169
|
+
kb_consistency_unresolved: KbConsistencyUnresolved
|
|
163
170
|
}.
|
|
164
171
|
|
|
165
172
|
%% check_must_priority_coverage(-Violations)
|
|
@@ -3663,7 +3670,9 @@ check_selected_dispatch(Rules, _{
|
|
|
3663
3670
|
domain_implication: DomainImplication,
|
|
3664
3671
|
subject_key_identity: SubjectKeyIdentity,
|
|
3665
3672
|
subject_key_shape: SubjectKeyShape,
|
|
3666
|
-
ontology_quality: OntologyQuality
|
|
3673
|
+
ontology_quality: OntologyQuality,
|
|
3674
|
+
kb_consistency: KbConsistency,
|
|
3675
|
+
kb_consistency_unresolved: KbConsistencyUnresolved
|
|
3667
3676
|
}) :-
|
|
3668
3677
|
selected_rule(Rules, 'must-priority-coverage', check_must_priority_coverage, MustPriority),
|
|
3669
3678
|
selected_rule(Rules, 'symbol-coverage', check_symbol_coverage, SymbolCoverage),
|
|
@@ -3698,7 +3707,9 @@ check_selected_dispatch(Rules, _{
|
|
|
3698
3707
|
selected_rule(Rules, 'domain-implication', check_domain_implication, DomainImplication),
|
|
3699
3708
|
selected_rule(Rules, 'subject-key-identity', check_subject_key_identity, SubjectKeyIdentity),
|
|
3700
3709
|
selected_rule(Rules, 'subject-key-shape', check_subject_key_shape, SubjectKeyShape),
|
|
3701
|
-
selected_rule(Rules, 'ontology-quality', check_ontology_quality, OntologyQuality)
|
|
3710
|
+
selected_rule(Rules, 'ontology-quality', check_ontology_quality, OntologyQuality),
|
|
3711
|
+
selected_rule(Rules, 'kb-consistency', check_kb_consistency, KbConsistency),
|
|
3712
|
+
selected_rule(Rules, 'kb-consistency-unresolved', check_kb_consistency_unresolved, KbConsistencyUnresolved).
|
|
3702
3713
|
|
|
3703
3714
|
selected_rule(Rules, Name, Goal, Violations) :-
|
|
3704
3715
|
( memberchk(Name, Rules)
|
|
@@ -3770,6 +3781,8 @@ check_all_with_options(ViolationsDict, RequireAdr) :-
|
|
|
3770
3781
|
check_subject_key_identity(SubjectKeyIdentity),
|
|
3771
3782
|
check_subject_key_shape(SubjectKeyShape),
|
|
3772
3783
|
check_ontology_quality(OntologyQuality),
|
|
3784
|
+
check_kb_consistency(KbConsistency),
|
|
3785
|
+
check_kb_consistency_unresolved(KbConsistencyUnresolved),
|
|
3773
3786
|
ViolationsDict = _{
|
|
3774
3787
|
must_priority_coverage: MustPriority,
|
|
3775
3788
|
symbol_coverage: SymbolCoverage,
|
|
@@ -3803,7 +3816,9 @@ check_all_with_options(ViolationsDict, RequireAdr) :-
|
|
|
3803
3816
|
domain_implication: DomainImplication,
|
|
3804
3817
|
subject_key_identity: SubjectKeyIdentity,
|
|
3805
3818
|
subject_key_shape: SubjectKeyShape,
|
|
3806
|
-
ontology_quality: OntologyQuality
|
|
3819
|
+
ontology_quality: OntologyQuality,
|
|
3820
|
+
kb_consistency: KbConsistency,
|
|
3821
|
+
kb_consistency_unresolved: KbConsistencyUnresolved
|
|
3807
3822
|
}.
|
|
3808
3823
|
|
|
3809
3824
|
%% violations_dict_to_json(+ViolationsDict, -JsonDict)
|
package/src/discovery.pl
CHANGED
|
@@ -398,7 +398,7 @@ coverage_report_json(By, Tags, IncludePassing, BindingMode, TestBindings, Includ
|
|
|
398
398
|
|
|
399
399
|
% implements REQ-kibi-conservative-requirement-proof
|
|
400
400
|
% Proof-status filtered report. The status filter selects requirement rows by
|
|
401
|
-
% proofStatus (
|
|
401
|
+
% proofStatus (tested, missing, unresolved, not_applicable) and replaces the
|
|
402
402
|
% default include-passing filtering, so callers can enumerate exactly one
|
|
403
403
|
% slice — e.g. every not_applicable row together with its typed applicability
|
|
404
404
|
% reason — without diffing full exports by hand. The summary is always
|
|
@@ -1127,7 +1127,7 @@ coverage_summary(Rows, Summary) :-
|
|
|
1127
1127
|
include(req_row_missing_scenario, Rows, MissingScenarioRows),
|
|
1128
1128
|
include(req_row_missing_test, Rows, MissingTestRows),
|
|
1129
1129
|
include(req_row_missing_both, Rows, MissingBothRows),
|
|
1130
|
-
include(
|
|
1130
|
+
include(req_row_proof_tested, Rows, ProofTestedRows),
|
|
1131
1131
|
include(req_row_proof_unresolved, Rows, ProofUnresolvedRows),
|
|
1132
1132
|
include(req_row_proof_missing, Rows, ProofMissingRows),
|
|
1133
1133
|
include(req_row_proof_not_applicable, Rows, ProofNotApplicableRows),
|
|
@@ -1138,7 +1138,8 @@ coverage_summary(Rows, Summary) :-
|
|
|
1138
1138
|
length(MissingScenarioRows, MissingScenario),
|
|
1139
1139
|
length(MissingTestRows, MissingTest),
|
|
1140
1140
|
length(MissingBothRows, MissingScenarioAndTest),
|
|
1141
|
-
length(
|
|
1141
|
+
length(ProofTestedRows, ProofTested),
|
|
1142
|
+
evidence_level_counts(Rows, EvidenceLevels),
|
|
1142
1143
|
length(ProofUnresolvedRows, ProofUnresolved),
|
|
1143
1144
|
length(ProofMissingRows, ProofMissing),
|
|
1144
1145
|
length(ProofNotApplicableRows, ProofNotApplicable),
|
|
@@ -1151,12 +1152,20 @@ coverage_summary(Rows, Summary) :-
|
|
|
1151
1152
|
missingScenario: MissingScenario,
|
|
1152
1153
|
missingTest: MissingTest,
|
|
1153
1154
|
missingScenarioAndTest: MissingScenarioAndTest,
|
|
1154
|
-
|
|
1155
|
+
proofTested: ProofTested,
|
|
1155
1156
|
proofUnresolved: ProofUnresolved,
|
|
1156
1157
|
proofMissing: ProofMissing,
|
|
1157
|
-
proofNotApplicable: ProofNotApplicable
|
|
1158
|
+
proofNotApplicable: ProofNotApplicable,
|
|
1159
|
+
evidenceLevels: EvidenceLevels
|
|
1158
1160
|
}.
|
|
1159
1161
|
|
|
1162
|
+
% implements REQ-kibi-evidence-levels
|
|
1163
|
+
evidence_level_counts(Rows, _{proved: Proved, tested: Tested, asserted: Asserted, unverified: Unverified}) :-
|
|
1164
|
+
aggregate_all(count, (member(Row, Rows), Row.get(evidenceLevel) == proved), Proved),
|
|
1165
|
+
aggregate_all(count, (member(Row, Rows), Row.get(evidenceLevel) == tested), Tested),
|
|
1166
|
+
aggregate_all(count, (member(Row, Rows), Row.get(evidenceLevel) == asserted), Asserted),
|
|
1167
|
+
aggregate_all(count, (member(Row, Rows), Row.get(evidenceLevel) == unverified), Unverified).
|
|
1168
|
+
|
|
1160
1169
|
req_row_fully_covered(Row) :-
|
|
1161
1170
|
CoverageStatus = Row.get(coverageStatus),
|
|
1162
1171
|
CoverageStatus = fully_covered.
|
|
@@ -1175,8 +1184,8 @@ req_row_missing_test(Row) :-
|
|
|
1175
1184
|
Row.gaps = [missing_test].
|
|
1176
1185
|
req_row_missing_both(Row) :-
|
|
1177
1186
|
Row.gaps = [missing_scenario_and_test].
|
|
1178
|
-
|
|
1179
|
-
Row.proofStatus ==
|
|
1187
|
+
req_row_proof_tested(Row) :-
|
|
1188
|
+
Row.proofStatus == tested.
|
|
1180
1189
|
req_row_proof_unresolved(Row) :-
|
|
1181
1190
|
Row.proofStatus == unresolved.
|
|
1182
1191
|
req_row_proof_missing(Row) :-
|
|
@@ -1377,7 +1386,7 @@ filter_req_coverage_rows(false, Rows, Filtered) :-
|
|
|
1377
1386
|
|
|
1378
1387
|
req_row_proof_passing(Row) :-
|
|
1379
1388
|
Status = Row.proofStatus,
|
|
1380
|
-
memberchk(Status, [
|
|
1389
|
+
memberchk(Status, [tested, not_applicable]).
|
|
1381
1390
|
|
|
1382
1391
|
filter_symbol_coverage_rows(true, Rows, Rows).
|
|
1383
1392
|
filter_symbol_coverage_rows(false, Rows, Filtered) :-
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
% Module: kb_consistency
|
|
2
|
+
% implements REQ-kibi-kb-certificates
|
|
3
|
+
% The kb-consistency check rules. Each run produces the certificate bundle
|
|
4
|
+
% (certificates.pl), round-trips it through JSON exactly as a saved bundle
|
|
5
|
+
% would be read, and re-checks it with the independent checker
|
|
6
|
+
% (certificate_check.pl). Only what the checker accepts becomes a finding:
|
|
7
|
+
%
|
|
8
|
+
% kb-consistency a re-verified contradiction (an irreducible
|
|
9
|
+
% core of property facts, two facts that
|
|
10
|
+
% disagree on an atom or a decision, or a
|
|
11
|
+
% negation cycle among stored rules), or a
|
|
12
|
+
% certificate the checker rejects
|
|
13
|
+
% kb-consistency-unresolved a context the checker cannot compare (mixed
|
|
14
|
+
% types or units, unsupported operators or
|
|
15
|
+
% polarities, windows that never overlap)
|
|
16
|
+
:- module(kb_consistency, [
|
|
17
|
+
check_kb_consistency/1,
|
|
18
|
+
check_kb_consistency_unresolved/1
|
|
19
|
+
]).
|
|
20
|
+
|
|
21
|
+
:- encoding(utf8).
|
|
22
|
+
|
|
23
|
+
:- use_module(library(apply)).
|
|
24
|
+
:- use_module(library(lists)).
|
|
25
|
+
:- use_module(library(http/json)).
|
|
26
|
+
:- use_module('kb.pl', [kb_entity/3, kb_relationship/3]).
|
|
27
|
+
:- use_module('certificates.pl', [kb_certificates/1]).
|
|
28
|
+
:- use_module('certificate_check.pl', [verify_certificates/2]).
|
|
29
|
+
|
|
30
|
+
%% check_kb_consistency(-Violations)
|
|
31
|
+
check_kb_consistency(Violations) :-
|
|
32
|
+
consistency_verification(Bundle, Report),
|
|
33
|
+
numbered_certificates(Bundle, Numbered),
|
|
34
|
+
findall(Index, (member(Failure, Report.rejected), get_dict(certificate, Failure, Index)), Rejected),
|
|
35
|
+
findall(Violation,
|
|
36
|
+
( member(Index-Certificate, Numbered),
|
|
37
|
+
\+ memberchk(Index, Rejected),
|
|
38
|
+
contradiction_violation(Certificate, Violation)
|
|
39
|
+
),
|
|
40
|
+
Contradictions),
|
|
41
|
+
findall(Violation,
|
|
42
|
+
( member(Failure, Report.rejected),
|
|
43
|
+
rejected_violation(Failure, Violation)
|
|
44
|
+
),
|
|
45
|
+
RejectedViolations),
|
|
46
|
+
append(Contradictions, RejectedViolations, Unsorted),
|
|
47
|
+
sort(Unsorted, Violations).
|
|
48
|
+
|
|
49
|
+
%% check_kb_consistency_unresolved(-Violations)
|
|
50
|
+
check_kb_consistency_unresolved(Violations) :-
|
|
51
|
+
consistency_verification(_Bundle, Report),
|
|
52
|
+
findall(Violation,
|
|
53
|
+
( member(Theorem, Report.theorems),
|
|
54
|
+
get_dict(unresolved, Theorem, Entries),
|
|
55
|
+
member(Entry, Entries),
|
|
56
|
+
unresolved_violation(Theorem.theorem, Entry, Violation)
|
|
57
|
+
),
|
|
58
|
+
Unsorted),
|
|
59
|
+
sort(Unsorted, Violations).
|
|
60
|
+
|
|
61
|
+
consistency_verification(Bundle, Report) :-
|
|
62
|
+
kb_certificates(Produced),
|
|
63
|
+
with_output_to(string(Json), json_write_dict(current_output, Produced, [width(0)])),
|
|
64
|
+
open_string(Json, Stream),
|
|
65
|
+
json_read_dict(Stream, Bundle, [value_string_as(string)]),
|
|
66
|
+
verify_certificates(Bundle, Report).
|
|
67
|
+
|
|
68
|
+
numbered_certificates(Bundle, Numbered) :-
|
|
69
|
+
findall(Index-Certificate, nth1(Index, Bundle.certificates, Certificate), Numbered).
|
|
70
|
+
|
|
71
|
+
% ------------------------------------------------------------------
|
|
72
|
+
% Re-verified contradictions
|
|
73
|
+
% ------------------------------------------------------------------
|
|
74
|
+
|
|
75
|
+
contradiction_violation(Certificate, violation('kb-consistency', EntityId, Description, Suggestion, "", _{certificate: Certificate})) :-
|
|
76
|
+
get_dict(kind, Certificate, KindText),
|
|
77
|
+
atom_string(Kind, KindText),
|
|
78
|
+
contradiction_text(Kind, Certificate, Facts, Description),
|
|
79
|
+
fact_requirements(Facts, Requirements),
|
|
80
|
+
entity_label(Requirements, Facts, EntityId),
|
|
81
|
+
suggestion_text(Kind, Requirements, Suggestion).
|
|
82
|
+
|
|
83
|
+
contradiction_text(property_contradiction, Certificate, Facts, Description) :-
|
|
84
|
+
Facts = Certificate.core,
|
|
85
|
+
context_text(Certificate, Context),
|
|
86
|
+
atomic_list_concat(Facts, ', ', FactText),
|
|
87
|
+
findall(Text,
|
|
88
|
+
( member(Drop, Certificate.dropModels),
|
|
89
|
+
format(string(Text), "~w: ~w", [Drop.factId, Drop.value])
|
|
90
|
+
),
|
|
91
|
+
DropTexts),
|
|
92
|
+
atomic_list_concat(DropTexts, ', ', DropText),
|
|
93
|
+
format(string(Description),
|
|
94
|
+
"Facts ~w cannot all hold for ~w: no value satisfies them together, and dropping any one leaves a value (~w)",
|
|
95
|
+
[FactText, Context, DropText]).
|
|
96
|
+
contradiction_text(predicate_contradiction, Certificate, Facts, Description) :-
|
|
97
|
+
Facts = Certificate.core,
|
|
98
|
+
Facts = [FactA, FactB],
|
|
99
|
+
atom_text(Certificate, Atom),
|
|
100
|
+
format(string(Description),
|
|
101
|
+
"Facts ~w and ~w give ~w opposite truth values", [FactA, FactB, Atom]).
|
|
102
|
+
contradiction_text(decision_contradiction, Certificate, Facts, Description) :-
|
|
103
|
+
Facts = Certificate.core,
|
|
104
|
+
Facts = [FactA, FactB],
|
|
105
|
+
decision_text(Certificate, Decision),
|
|
106
|
+
format(string(Description),
|
|
107
|
+
"Facts ~w and ~w decide ~w differently: one allows it and the other denies it", [FactA, FactB, Decision]).
|
|
108
|
+
contradiction_text(rule_negation_cycle, Certificate, [], Description) :-
|
|
109
|
+
findall(Text,
|
|
110
|
+
( member(Edge, Certificate.cycle),
|
|
111
|
+
signature_text(Edge.head, Head),
|
|
112
|
+
signature_text(Edge.body, Body),
|
|
113
|
+
( atom_string(negative, Edge.sign) -> Arrow = "depends on not" ; Arrow = "depends on" ),
|
|
114
|
+
format(string(Text), "~w ~w ~w", [Head, Arrow, Body])
|
|
115
|
+
),
|
|
116
|
+
Texts),
|
|
117
|
+
atomic_list_concat(Texts, "; ", CycleText),
|
|
118
|
+
format(string(Description),
|
|
119
|
+
"Stored rules are not stratified: a predicate depends on its own negation (~w)", [CycleText]).
|
|
120
|
+
|
|
121
|
+
suggestion_text(rule_negation_cycle, _Requirements,
|
|
122
|
+
"Break the cycle: change one rule so no predicate depends on its own negation, or supersede the requirement that stores it") :- !.
|
|
123
|
+
suggestion_text(_Kind, Requirements, Suggestion) :-
|
|
124
|
+
atomic_list_concat(Requirements, ', ', RequirementText),
|
|
125
|
+
format(string(Suggestion),
|
|
126
|
+
"Supersede or change one of ~w so the facts can hold together; kibi check --write-certificates <file> saves the certificate",
|
|
127
|
+
[RequirementText]).
|
|
128
|
+
|
|
129
|
+
% ------------------------------------------------------------------
|
|
130
|
+
% Rejected certificates and unresolved contexts
|
|
131
|
+
% ------------------------------------------------------------------
|
|
132
|
+
|
|
133
|
+
rejected_violation(Failure, violation('kb-consistency', "kb-certificates", Description,
|
|
134
|
+
"Kibi produced a certificate its own checker rejects. Report it with the bundle from kibi check --write-certificates <file>", "")) :-
|
|
135
|
+
( get_dict(group, Failure, Group)
|
|
136
|
+
-> group_text(Group, Where)
|
|
137
|
+
; format(string(Where), "certificate ~w (~w)", [Failure.certificate, Failure.kind])
|
|
138
|
+
),
|
|
139
|
+
format(string(Description),
|
|
140
|
+
"The independent checker rejected ~w: ~w", [Where, Failure.reason]).
|
|
141
|
+
|
|
142
|
+
unresolved_violation(_Theorem, Entry, violation('kb-consistency-unresolved', EntityId, Description, Suggestion, "")) :-
|
|
143
|
+
group_text(Entry, Where),
|
|
144
|
+
( get_dict(reason, Entry, Reason)
|
|
145
|
+
-> true
|
|
146
|
+
; Reason = "the contradicting facts' validity windows never overlap"
|
|
147
|
+
),
|
|
148
|
+
entry_facts(Entry, Facts),
|
|
149
|
+
fact_requirements(Facts, Requirements),
|
|
150
|
+
entity_label(Requirements, Facts, EntityId0),
|
|
151
|
+
( EntityId0 == "" -> EntityId = Where ; EntityId = EntityId0 ),
|
|
152
|
+
format(string(Description),
|
|
153
|
+
"Kibi cannot decide whether the facts for ~w hold together: ~w. It never counts as consistent", [Where, Reason]),
|
|
154
|
+
Suggestion = "Align the facts' value_type, unit, operator or polarity so they can be compared, or accept that this context stays unchecked".
|
|
155
|
+
|
|
156
|
+
entry_facts(Entry, Facts) :-
|
|
157
|
+
get_dict(subjectKey, Entry, Subject),
|
|
158
|
+
!,
|
|
159
|
+
get_dict(propertyKey, Entry, Property),
|
|
160
|
+
findall(Fact,
|
|
161
|
+
( kb:current_req(Req),
|
|
162
|
+
kb:effective_req_property_fact(Req, Subject, Fact, Property, _, _, _, _, _, _, _, _)
|
|
163
|
+
),
|
|
164
|
+
Facts0),
|
|
165
|
+
sort(Facts0, Facts).
|
|
166
|
+
entry_facts(_Entry, []).
|
|
167
|
+
|
|
168
|
+
% ------------------------------------------------------------------
|
|
169
|
+
% Text helpers
|
|
170
|
+
% ------------------------------------------------------------------
|
|
171
|
+
|
|
172
|
+
fact_requirements(Facts, Requirements) :-
|
|
173
|
+
findall(Req,
|
|
174
|
+
( member(FactText, Facts),
|
|
175
|
+
atom_string(Fact, FactText),
|
|
176
|
+
member(Type, [requires_property, requires_predicate, requires_rule]),
|
|
177
|
+
kb_relationship(Type, Req, Fact),
|
|
178
|
+
kb:current_req(Req)
|
|
179
|
+
),
|
|
180
|
+
Requirements0),
|
|
181
|
+
sort(Requirements0, Requirements).
|
|
182
|
+
|
|
183
|
+
entity_label([], [], "") :- !.
|
|
184
|
+
entity_label([], Facts, Label) :- !, atomic_list_concat(Facts, '/', Label0), atom_string(Label0, Label).
|
|
185
|
+
entity_label(Requirements, _Facts, Label) :- atomic_list_concat(Requirements, '/', Label0), atom_string(Label0, Label).
|
|
186
|
+
|
|
187
|
+
context_text(Label, Text) :-
|
|
188
|
+
( get_dict(scope, Label, Scope0) -> atom_string(Scope0, Scope) ; Scope = "" ),
|
|
189
|
+
( Scope == ""
|
|
190
|
+
-> format(string(Text), "~w.~w", [Label.subjectKey, Label.propertyKey])
|
|
191
|
+
; format(string(Text), "~w.~w (scope ~w)", [Label.subjectKey, Label.propertyKey, Scope])
|
|
192
|
+
).
|
|
193
|
+
|
|
194
|
+
group_text(Group, Text) :-
|
|
195
|
+
( get_dict(subjectKey, Group, _)
|
|
196
|
+
-> context_text(Group, Text)
|
|
197
|
+
; get_dict(args, Group, _)
|
|
198
|
+
-> atom_text(Group, Text)
|
|
199
|
+
; get_dict(key, Group, _)
|
|
200
|
+
-> decision_text(Group, Text)
|
|
201
|
+
; format(string(Text), "~w", [Group])
|
|
202
|
+
).
|
|
203
|
+
|
|
204
|
+
atom_text(Label, Text) :-
|
|
205
|
+
atomic_list_concat(Label.args, ', ', Args),
|
|
206
|
+
format(string(Text), "~w:~w(~w)", [Label.namespace, Label.name, Args]).
|
|
207
|
+
|
|
208
|
+
decision_text(Label, Text) :-
|
|
209
|
+
atomic_list_concat(Label.key, ', ', Key),
|
|
210
|
+
format(string(Text), "~w:~w/~w for (~w)", [Label.namespace, Label.name, Label.arity, Key]).
|
|
211
|
+
|
|
212
|
+
signature_text(Signature, Text) :-
|
|
213
|
+
format(string(Text), "~w:~w/~w", [Signature.namespace, Signature.name, Signature.arity]).
|