kibi-core 0.22.4 → 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.pl +32 -19
- 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) :-
|
package/src/kb.pl
CHANGED
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
kb_commit_upsert/5,
|
|
17
17
|
kb_commit_upsert_batch/2,
|
|
18
18
|
kb_preview_upsert_contradiction/3,
|
|
19
|
+
kb_preview_upsert_contradiction/4,
|
|
19
20
|
kb_log_entity_upsert/3,
|
|
20
21
|
kb_retract_entity/1,
|
|
21
22
|
kb_retract_entity/3,
|
|
@@ -1069,9 +1070,7 @@ kb_commit_upsert(Type, Props, Relationships, SkipContradiction, ChangeKind) :-
|
|
|
1069
1070
|
catch(
|
|
1070
1071
|
rdf_transaction((
|
|
1071
1072
|
kb_stage(rdf_mutation),
|
|
1072
|
-
|
|
1073
|
-
kb_commit_relationships_no_audit(Relationships),
|
|
1074
|
-
kb_maybe_check_req_contradiction(Type, Id, SkipContradiction),
|
|
1073
|
+
kb_stage_upsert_mutation(Type, Id, Props, Relationships, SkipContradiction),
|
|
1075
1074
|
kb_stage(entity_audit),
|
|
1076
1075
|
kb_log_entity_upsert(ChangeKind, Type, Props),
|
|
1077
1076
|
kb_stage(relationship_audit),
|
|
@@ -1101,9 +1100,7 @@ kb_commit_upsert(Type, Props, Relationships, SkipContradiction, ChangeKind) :-
|
|
|
1101
1100
|
upsert_change_kind(Id, ChangeKind),
|
|
1102
1101
|
rdf_transaction((
|
|
1103
1102
|
kb_stage(rdf_mutation),
|
|
1104
|
-
|
|
1105
|
-
kb_commit_relationships_no_audit(Relationships),
|
|
1106
|
-
kb_maybe_check_req_contradiction(Type, Id, SkipContradiction),
|
|
1103
|
+
kb_stage_upsert_mutation(Type, Id, Props, Relationships, SkipContradiction),
|
|
1107
1104
|
kb_stage(entity_audit),
|
|
1108
1105
|
kb_log_entity_upsert(ChangeKind, Type, Props),
|
|
1109
1106
|
kb_stage(relationship_audit),
|
|
@@ -1130,35 +1127,51 @@ kb_maybe_check_req_contradiction(req, Id, false) :-
|
|
|
1130
1127
|
check_req_contradiction(Id).
|
|
1131
1128
|
kb_maybe_check_req_contradiction(_, _, _).
|
|
1132
1129
|
|
|
1130
|
+
%% kb_stage_upsert_mutation(+Type, +Id, +Properties, +Relationships, +SkipContradiction)
|
|
1131
|
+
% implements REQ-kibi-operation-interface-parity
|
|
1132
|
+
% The RDF mutation and refusal rules of one upsert, shared by the commit, the
|
|
1133
|
+
% batch commit and the dry-run preview so all three refuse the same payloads:
|
|
1134
|
+
% a relationship whose source or target entity does not exist, an invalid
|
|
1135
|
+
% relationship direction, a symbol mixing executable_for with production
|
|
1136
|
+
% ownership/coverage edges, and (for a requirement) a contradiction.
|
|
1137
|
+
kb_stage_upsert_mutation(Type, Id, Props, Relationships, SkipContradiction) :-
|
|
1138
|
+
kb_assert_entity_no_audit(Type, Props),
|
|
1139
|
+
kb_commit_relationships_no_audit(Relationships),
|
|
1140
|
+
kb_maybe_check_req_contradiction(Type, Id, SkipContradiction).
|
|
1141
|
+
|
|
1133
1142
|
% Stage one upsert inside an already-open transaction. The caller saves once.
|
|
1134
1143
|
kb_stage_upsert(Type, Props, Relationships, SkipContradiction, ChangeKind) :-
|
|
1135
1144
|
memberchk(id=Id, Props),
|
|
1136
1145
|
memberchk(SkipContradiction, [true, false]),
|
|
1137
1146
|
upsert_change_kind(Id, ChangeKind),
|
|
1138
|
-
|
|
1139
|
-
kb_commit_relationships_no_audit(Relationships),
|
|
1140
|
-
kb_maybe_check_req_contradiction(Type, Id, SkipContradiction),
|
|
1147
|
+
kb_stage_upsert_mutation(Type, Id, Props, Relationships, SkipContradiction),
|
|
1141
1148
|
kb_log_entity_upsert(ChangeKind, Type, Props),
|
|
1142
1149
|
kb_commit_relationship_audits(Relationships).
|
|
1143
1150
|
|
|
1144
1151
|
%% kb_preview_upsert_contradiction(+Type, +Properties, +Relationships)
|
|
1152
|
+
%% kb_preview_upsert_contradiction(+Type, +Properties, +Relationships, +SkipContradiction)
|
|
1145
1153
|
% implements REQ-kibi-operation-interface-parity
|
|
1146
|
-
% Stage one upsert in a transaction that is always rolled back and run
|
|
1147
|
-
% commit-time
|
|
1148
|
-
% kb_upsert dry run refuses exactly what the commit would refuse
|
|
1149
|
-
%
|
|
1150
|
-
%
|
|
1151
|
-
%
|
|
1152
|
-
%
|
|
1154
|
+
% Stage one upsert in a transaction that is always rolled back and run every
|
|
1155
|
+
% commit-time refusal rule (kb_stage_upsert_mutation/5) against the staged
|
|
1156
|
+
% KB, so a kb_upsert dry run refuses exactly what the commit would refuse:
|
|
1157
|
+
% missing relationship endpoints, invalid directions, mixed symbol roles and,
|
|
1158
|
+
% unless skipped, requirement contradictions. Nothing is written and nothing
|
|
1159
|
+
% marks the branch dirty: the no-audit assertions only touch the RDF store,
|
|
1160
|
+
% which the transaction rolls back, and no audit entry, snapshot save or
|
|
1161
|
+
% journal flush follows. The entity index is not transactional, so the
|
|
1162
|
+
% staged id is re-indexed from the restored store.
|
|
1153
1163
|
kb_preview_upsert_contradiction(Type, Props, Relationships) :-
|
|
1164
|
+
kb_preview_upsert_contradiction(Type, Props, Relationships, false).
|
|
1165
|
+
|
|
1166
|
+
kb_preview_upsert_contradiction(Type, Props, Relationships, SkipContradiction) :-
|
|
1154
1167
|
memberchk(id=Id, Props),
|
|
1168
|
+
memberchk(SkipContradiction, [true, false]),
|
|
1155
1169
|
with_kb_mutex(
|
|
1156
1170
|
call_cleanup(
|
|
1157
1171
|
catch(
|
|
1158
1172
|
rdf_transaction((
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
kb_maybe_check_req_contradiction(Type, Id, false),
|
|
1173
|
+
kb_stage(rdf_mutation),
|
|
1174
|
+
kb_stage_upsert_mutation(Type, Id, Props, Relationships, SkipContradiction),
|
|
1162
1175
|
throw(kibi_preview_rolled_back)
|
|
1163
1176
|
)),
|
|
1164
1177
|
Caught,
|