kibi-core 0.6.4 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/schema/entities.pl +8 -0
- package/schema/validation.pl +22 -1
- package/src/checks.pl +193 -11
- package/src/discovery.pl +127 -1
- package/src/kb.pl +22 -1
- package/src/sparql_client.pl +23 -1
- package/src/status.pl +15 -0
package/package.json
CHANGED
package/schema/entities.pl
CHANGED
|
@@ -29,6 +29,7 @@ entity_property(_, severity, atom).
|
|
|
29
29
|
entity_property(_, links, list).
|
|
30
30
|
entity_property(_, text_ref, uri).
|
|
31
31
|
entity_property(_, sourceFile, uri).
|
|
32
|
+
entity_property(req, logic_claims, list).
|
|
32
33
|
|
|
33
34
|
% Typed fact fields - only valid for fact entities
|
|
34
35
|
entity_property(fact, fact_kind, atom).
|
|
@@ -47,6 +48,8 @@ entity_property(fact, closed_world, boolean).
|
|
|
47
48
|
entity_property(fact, valid_from, datetime).
|
|
48
49
|
entity_property(fact, valid_to, datetime).
|
|
49
50
|
entity_property(fact, canonical_key, string).
|
|
51
|
+
entity_property(fact, claim_key, string).
|
|
52
|
+
entity_property(fact, claim_text, string).
|
|
50
53
|
entity_property(fact, predicate_name, string).
|
|
51
54
|
entity_property(fact, predicate_namespace, string).
|
|
52
55
|
entity_property(fact, predicate_arity, integer).
|
|
@@ -57,6 +60,10 @@ entity_property(fact, aliases, list).
|
|
|
57
60
|
entity_property(fact, examples, list).
|
|
58
61
|
entity_property(fact, predicate_args, list).
|
|
59
62
|
|
|
63
|
+
% Typed symbol metadata fields - only valid for symbol entities
|
|
64
|
+
entity_property(symbol, symbol_role, atom).
|
|
65
|
+
entity_property(symbol, granularity_reason, atom).
|
|
66
|
+
|
|
60
67
|
% Typed test verification fields - only valid for test entities
|
|
61
68
|
entity_property(test, verification_scope, atom).
|
|
62
69
|
entity_property(test, verification_perspective, atom).
|
|
@@ -76,6 +83,7 @@ optional_property(Type, priority) :- entity_type(Type).
|
|
|
76
83
|
optional_property(Type, severity) :- entity_type(Type).
|
|
77
84
|
optional_property(Type, links) :- entity_type(Type).
|
|
78
85
|
optional_property(Type, text_ref) :- entity_type(Type).
|
|
86
|
+
optional_property(req, logic_claims).
|
|
79
87
|
optional_property(test, verification_scope).
|
|
80
88
|
optional_property(test, verification_perspective).
|
|
81
89
|
|
package/schema/validation.pl
CHANGED
|
@@ -59,17 +59,24 @@ validate_entity_shape(fact, Props) :-
|
|
|
59
59
|
!,
|
|
60
60
|
valid_optional_fact_enums(Props),
|
|
61
61
|
valid_polarity_in_props(Props),
|
|
62
|
+
valid_claim_provenance(Props),
|
|
62
63
|
( memberchk(fact_kind=RawKind, Props) -> validate_fact_shape(RawKind, Props) ; true ).
|
|
63
64
|
validate_entity_shape(test, Props) :-
|
|
64
65
|
!,
|
|
65
66
|
valid_optional_test_enums(Props),
|
|
66
67
|
forall(member(Key=_, Props), \+ is_fact_only_field(Key)).
|
|
68
|
+
validate_entity_shape(req, Props) :-
|
|
69
|
+
!,
|
|
70
|
+
forall(member(Key=_, Props), \+ is_fact_only_field(Key)),
|
|
71
|
+
forall(member(Key=_, Props), \+ is_test_only_field(Key)).
|
|
67
72
|
validate_entity_shape(Type, Props) :-
|
|
68
73
|
Type \= fact,
|
|
69
74
|
Type \= test,
|
|
75
|
+
Type \= req,
|
|
70
76
|
% Non-fact/non-test entities cannot have type-specific fields
|
|
71
77
|
forall(member(Key=_, Props), \+ is_fact_only_field(Key)),
|
|
72
|
-
forall(member(Key=_, Props), \+ is_test_only_field(Key))
|
|
78
|
+
forall(member(Key=_, Props), \+ is_test_only_field(Key)),
|
|
79
|
+
forall(member(Key=_, Props), Key \= logic_claims).
|
|
73
80
|
|
|
74
81
|
% is_fact_only_field(+Key) - true if Key is a fact-specific field
|
|
75
82
|
is_fact_only_field(fact_kind).
|
|
@@ -88,6 +95,8 @@ is_fact_only_field(closed_world).
|
|
|
88
95
|
is_fact_only_field(valid_from).
|
|
89
96
|
is_fact_only_field(valid_to).
|
|
90
97
|
is_fact_only_field(canonical_key).
|
|
98
|
+
is_fact_only_field(claim_key).
|
|
99
|
+
is_fact_only_field(claim_text).
|
|
91
100
|
is_fact_only_field(predicate_name).
|
|
92
101
|
is_fact_only_field(predicate_namespace).
|
|
93
102
|
is_fact_only_field(predicate_arity).
|
|
@@ -172,6 +181,18 @@ valid_strict_polarity_in_props(Props) :-
|
|
|
172
181
|
valid_predicate_polarity_in_props(Props) :-
|
|
173
182
|
( memberchk(polarity=P, Props) -> valid_predicate_polarity(P) ; true ).
|
|
174
183
|
|
|
184
|
+
% claim_key and claim_text form one auditable provenance pair.
|
|
185
|
+
valid_claim_provenance(Props) :-
|
|
186
|
+
( memberchk(claim_key=Key, Props)
|
|
187
|
+
-> memberchk(claim_text=Text, Props),
|
|
188
|
+
nonempty_claim_value(Key),
|
|
189
|
+
nonempty_claim_value(Text)
|
|
190
|
+
; \+ memberchk(claim_text=_, Props)
|
|
191
|
+
).
|
|
192
|
+
|
|
193
|
+
nonempty_claim_value(Value) :- string(Value), Value \= "".
|
|
194
|
+
nonempty_claim_value(Value) :- atom(Value), Value \= ''.
|
|
195
|
+
|
|
175
196
|
% valid_optional_fact_enums(+Props)
|
|
176
197
|
% Validates enum-typed fact fields whenever they are present
|
|
177
198
|
valid_optional_fact_enums(Props) :-
|
package/src/checks.pl
CHANGED
|
@@ -17,6 +17,8 @@
|
|
|
17
17
|
check_strict_fact_shape/1, % Returns list of malformed strict fact violations
|
|
18
18
|
check_strict_req_fact_pairing/1,% Returns list of malformed strict req/fact pairing violations
|
|
19
19
|
check_strict_readiness/1, % Returns list of strict readiness audit violations
|
|
20
|
+
check_predicate_verifiability/1,% Returns list of unverifiable predicate-lane requirement links
|
|
21
|
+
check_logic_coverage/1, % Returns list of incomplete declared logical claim manifests
|
|
20
22
|
run_checks_json/0, % Entry point for JSON output
|
|
21
23
|
violation_id_text/2 % Extract text from entity ID term (exported for testing)
|
|
22
24
|
]).
|
|
@@ -52,6 +54,8 @@ check_all(ViolationsDict) :-
|
|
|
52
54
|
check_strict_fact_shape(StrictFactShape),
|
|
53
55
|
check_strict_req_fact_pairing(StrictReqFactPairing),
|
|
54
56
|
check_strict_readiness(StrictReadiness),
|
|
57
|
+
check_predicate_verifiability(PredicateVerifiability),
|
|
58
|
+
check_logic_coverage(LogicCoverage),
|
|
55
59
|
ViolationsDict = _{
|
|
56
60
|
must_priority_coverage: MustPriority,
|
|
57
61
|
symbol_coverage: SymbolCoverage,
|
|
@@ -63,7 +67,9 @@ check_all(ViolationsDict) :-
|
|
|
63
67
|
domain_contradictions: Contradictions,
|
|
64
68
|
strict_fact_shape: StrictFactShape,
|
|
65
69
|
strict_req_fact_pairing: StrictReqFactPairing,
|
|
66
|
-
strict_readiness: StrictReadiness
|
|
70
|
+
strict_readiness: StrictReadiness,
|
|
71
|
+
predicate_verifiability: PredicateVerifiability,
|
|
72
|
+
logic_coverage: LogicCoverage
|
|
67
73
|
}.
|
|
68
74
|
|
|
69
75
|
%% check_must_priority_coverage(-Violations)
|
|
@@ -132,7 +138,6 @@ symbol_traceability_violation(RequireAdr, violation(
|
|
|
132
138
|
Source
|
|
133
139
|
)) :-
|
|
134
140
|
kb_entity(SymbolId, symbol, _),
|
|
135
|
-
\+ executable_test_symbol(SymbolId),
|
|
136
141
|
% Check if symbol has direct requirement ownership
|
|
137
142
|
( symbol_owns_requirement(SymbolId, ReqId),
|
|
138
143
|
kb_entity(ReqId, req, _)
|
|
@@ -148,6 +153,7 @@ symbol_traceability_violation(RequireAdr, violation(
|
|
|
148
153
|
)
|
|
149
154
|
; HasAdr = true % Not required, so pass this check
|
|
150
155
|
),
|
|
156
|
+
\+ executable_test_symbol(SymbolId),
|
|
151
157
|
% Determine what is missing
|
|
152
158
|
( HasReq = false, HasAdr = false, RequireAdr = true ->
|
|
153
159
|
Description = "Symbol has no direct requirement ownership and no ADR constraint.",
|
|
@@ -307,15 +313,14 @@ missing_required_field(Required, violation(
|
|
|
307
313
|
)) :-
|
|
308
314
|
kb_entity(EntityId, _Type, Props),
|
|
309
315
|
member(Field, Required),
|
|
310
|
-
\+ memberchk(Field=_, Props),
|
|
311
|
-
|
|
312
|
-
format(string(Description), "Missing required field: ~w", [Field]),
|
|
313
|
-
format(string(Suggestion), "Add ~w to entity definition", [Field]),
|
|
314
|
-
|
|
315
316
|
( memberchk(source=Source, Props)
|
|
316
317
|
-> true
|
|
317
318
|
; Source = ""
|
|
318
|
-
)
|
|
319
|
+
),
|
|
320
|
+
\+ memberchk(Field=_, Props),
|
|
321
|
+
|
|
322
|
+
format(string(Description), "Missing required field: ~w", [Field]),
|
|
323
|
+
format(string(Suggestion), "Add ~w to entity definition", [Field]).
|
|
319
324
|
|
|
320
325
|
%% check_deprecated_adrs(-Violations)
|
|
321
326
|
% Finds all deprecated ADRs without successors.
|
|
@@ -428,8 +433,7 @@ property_value_shape_error(Props, "Property value fact missing required field: v
|
|
|
428
433
|
\+ memberchk(value_type=_, Props).
|
|
429
434
|
property_value_shape_error(Props, "Property value fact missing value field (value_string, value_int, value_number, or value_bool)") :-
|
|
430
435
|
memberchk(value_type=_, Props),
|
|
431
|
-
\+ (
|
|
432
|
-
memberchk(value_number=_, Props); memberchk(value_bool=_, Props)).
|
|
436
|
+
\+ has_value_field(Props).
|
|
433
437
|
property_value_shape_error(Props, "Property value fact has multiple value fields (should have exactly one)") :-
|
|
434
438
|
findall(F, (member(F=_, Props), is_value_field(F)), Fields),
|
|
435
439
|
length(Fields, Count),
|
|
@@ -445,6 +449,11 @@ is_value_field(value_int).
|
|
|
445
449
|
is_value_field(value_number).
|
|
446
450
|
is_value_field(value_bool).
|
|
447
451
|
|
|
452
|
+
has_value_field(Props) :- memberchk(value_string=_, Props), !.
|
|
453
|
+
has_value_field(Props) :- memberchk(value_int=_, Props), !.
|
|
454
|
+
has_value_field(Props) :- memberchk(value_number=_, Props), !.
|
|
455
|
+
has_value_field(Props) :- memberchk(value_bool=_, Props), !.
|
|
456
|
+
|
|
448
457
|
% value_type_matches_field(+ValueType, +Props)
|
|
449
458
|
value_type_matches_field(string, Props) :- memberchk(value_string=_, Props), !.
|
|
450
459
|
value_type_matches_field(int, Props) :- memberchk(value_int=_, Props), !.
|
|
@@ -752,6 +761,175 @@ strict_readiness_level(ReqId, traceable) :-
|
|
|
752
761
|
!.
|
|
753
762
|
strict_readiness_level(_ReqId, prose_only).
|
|
754
763
|
|
|
764
|
+
%% check_predicate_verifiability(-Violations)
|
|
765
|
+
% Finds requirements whose requires_predicate links do not target ground
|
|
766
|
+
% predicate facts. This keeps ontology-lane prose from looking modeled when the
|
|
767
|
+
% target is still a legacy, observation, meta, schema, or strict-property fact.
|
|
768
|
+
check_predicate_verifiability(Violations) :-
|
|
769
|
+
findall(
|
|
770
|
+
Violation,
|
|
771
|
+
predicate_verifiability_violation(Violation),
|
|
772
|
+
Violations0
|
|
773
|
+
),
|
|
774
|
+
sort(Violations0, Violations).
|
|
775
|
+
|
|
776
|
+
predicate_verifiability_violation(violation(
|
|
777
|
+
'predicate-verifiability',
|
|
778
|
+
ReqId,
|
|
779
|
+
Description,
|
|
780
|
+
"Apply kb_suggest_predicates and link requires_predicate to a fact_kind: predicate fact, or record a review:ontology-gap observation when no predicate fits",
|
|
781
|
+
Source
|
|
782
|
+
)) :-
|
|
783
|
+
kb:current_req(ReqId),
|
|
784
|
+
kb_relationship(requires_predicate, ReqId, FactId),
|
|
785
|
+
kb_entity(FactId, fact, Props),
|
|
786
|
+
predicate_verifiability_fact_kind(Props, Kind),
|
|
787
|
+
Kind \= predicate,
|
|
788
|
+
predicate_verifiability_kind_label(Kind, KindLabel),
|
|
789
|
+
format(
|
|
790
|
+
string(Description),
|
|
791
|
+
"Requirement uses requires_predicate ~w but target is ~w, not fact_kind=predicate",
|
|
792
|
+
[FactId, KindLabel]
|
|
793
|
+
),
|
|
794
|
+
violation_source(ReqId, req, Source).
|
|
795
|
+
|
|
796
|
+
predicate_verifiability_fact_kind(Props, Kind) :-
|
|
797
|
+
( memberchk(fact_kind=RawKind, Props)
|
|
798
|
+
-> normalize_term_atom(RawKind, Kind)
|
|
799
|
+
; Kind = legacy
|
|
800
|
+
).
|
|
801
|
+
|
|
802
|
+
predicate_verifiability_kind_label(legacy, "a legacy fact without fact_kind").
|
|
803
|
+
predicate_verifiability_kind_label(Kind, Label) :-
|
|
804
|
+
format(string(Label), "fact_kind=~w", [Kind]).
|
|
805
|
+
|
|
806
|
+
%% check_logic_coverage(-Violations)
|
|
807
|
+
% Validates explicitly declared atomic requirement claim manifests. Legacy
|
|
808
|
+
% requirements without logic_claims remain eligible for gradual backfill; once
|
|
809
|
+
% a manifest exists, every key must be grounded by a linked strict-property or
|
|
810
|
+
% predicate fact carrying the same claim_key.
|
|
811
|
+
check_logic_coverage(Violations) :-
|
|
812
|
+
findall(Violation, logic_coverage_violation(Violation), Violations0),
|
|
813
|
+
sort(Violations0, Violations).
|
|
814
|
+
|
|
815
|
+
logic_coverage_violation(violation(
|
|
816
|
+
'logic-coverage',
|
|
817
|
+
ReqId,
|
|
818
|
+
Description,
|
|
819
|
+
"Ground every declared claim with a property_value fact via requires_property or a predicate fact via requires_predicate, preserving the same claim_key",
|
|
820
|
+
Source
|
|
821
|
+
)) :-
|
|
822
|
+
kb:current_req(ReqId),
|
|
823
|
+
requirement_logic_claims(ReqId, ClaimKeys),
|
|
824
|
+
member(ClaimKey, ClaimKeys),
|
|
825
|
+
\+ grounded_requirement_claim(ReqId, ClaimKey),
|
|
826
|
+
format(
|
|
827
|
+
string(Description),
|
|
828
|
+
"Requirement declares logical claim ~w but has no matching ground fact",
|
|
829
|
+
[ClaimKey]
|
|
830
|
+
),
|
|
831
|
+
violation_source(ReqId, req, Source).
|
|
832
|
+
|
|
833
|
+
logic_coverage_violation(violation(
|
|
834
|
+
'logic-coverage',
|
|
835
|
+
ReqId,
|
|
836
|
+
Description,
|
|
837
|
+
"Keep a one-to-one mapping between each atomic claim key and its linked ground fact; split compound clauses before grounding",
|
|
838
|
+
Source
|
|
839
|
+
)) :-
|
|
840
|
+
kb:current_req(ReqId),
|
|
841
|
+
grounded_requirement_claim_fact(ReqId, ClaimKey, FactA),
|
|
842
|
+
grounded_requirement_claim_fact(ReqId, ClaimKey, FactB),
|
|
843
|
+
FactA @< FactB,
|
|
844
|
+
format(
|
|
845
|
+
string(Description),
|
|
846
|
+
"Requirement grounds logical claim ~w more than once through ~w and ~w",
|
|
847
|
+
[ClaimKey, FactA, FactB]
|
|
848
|
+
),
|
|
849
|
+
violation_source(ReqId, req, Source).
|
|
850
|
+
|
|
851
|
+
logic_coverage_violation(violation(
|
|
852
|
+
'logic-coverage',
|
|
853
|
+
ReqId,
|
|
854
|
+
Description,
|
|
855
|
+
"Remove punctuation or wording variants from the atomic inventory and keep one claim key for each distinct logical term",
|
|
856
|
+
Source
|
|
857
|
+
)) :-
|
|
858
|
+
kb:current_req(ReqId),
|
|
859
|
+
grounded_requirement_claim_fact(ReqId, ClaimA, FactA),
|
|
860
|
+
grounded_requirement_claim_fact(ReqId, ClaimB, FactB),
|
|
861
|
+
ClaimA \= ClaimB,
|
|
862
|
+
FactA @< FactB,
|
|
863
|
+
logical_ground_signature(FactA, Signature),
|
|
864
|
+
logical_ground_signature(FactB, Signature),
|
|
865
|
+
format(
|
|
866
|
+
string(Description),
|
|
867
|
+
"Requirement declares duplicate logical ground term ~w through claim keys ~w and ~w",
|
|
868
|
+
[Signature, ClaimA, ClaimB]
|
|
869
|
+
),
|
|
870
|
+
violation_source(ReqId, req, Source).
|
|
871
|
+
|
|
872
|
+
logic_coverage_violation(violation(
|
|
873
|
+
'logic-coverage',
|
|
874
|
+
ReqId,
|
|
875
|
+
Description,
|
|
876
|
+
"Append the linked fact claim_key to the requirement logic_claims manifest, or remove the stale logical link",
|
|
877
|
+
Source
|
|
878
|
+
)) :-
|
|
879
|
+
kb:current_req(ReqId),
|
|
880
|
+
requirement_logic_claims(ReqId, ClaimKeys),
|
|
881
|
+
grounded_requirement_claim(ReqId, ClaimKey),
|
|
882
|
+
\+ memberchk(ClaimKey, ClaimKeys),
|
|
883
|
+
format(
|
|
884
|
+
string(Description),
|
|
885
|
+
"Requirement links ground logical claim ~w but omits it from logic_claims",
|
|
886
|
+
[ClaimKey]
|
|
887
|
+
),
|
|
888
|
+
violation_source(ReqId, req, Source).
|
|
889
|
+
|
|
890
|
+
requirement_logic_claims(ReqId, ClaimKeys) :-
|
|
891
|
+
kb_entity(ReqId, req, Props),
|
|
892
|
+
memberchk(logic_claims=RawClaimKeys, Props),
|
|
893
|
+
kb:normalize_term_atom_list(RawClaimKeys, ClaimKeys).
|
|
894
|
+
|
|
895
|
+
grounded_requirement_claim(ReqId, ClaimKey) :-
|
|
896
|
+
grounded_requirement_claim_fact(ReqId, ClaimKey, _FactId).
|
|
897
|
+
|
|
898
|
+
grounded_requirement_claim_fact(ReqId, ClaimKey, FactId) :-
|
|
899
|
+
kb_relationship(requires_property, ReqId, FactId),
|
|
900
|
+
ground_fact_claim_key(FactId, property_value, ClaimKey).
|
|
901
|
+
grounded_requirement_claim_fact(ReqId, ClaimKey, FactId) :-
|
|
902
|
+
kb_relationship(requires_predicate, ReqId, FactId),
|
|
903
|
+
ground_fact_claim_key(FactId, predicate, ClaimKey).
|
|
904
|
+
|
|
905
|
+
logical_ground_signature(FactId, predicate(Namespace, Name, Args, Polarity)) :-
|
|
906
|
+
kb:predicate_fact(FactId, Namespace, Name, Args, Polarity).
|
|
907
|
+
logical_ground_signature(
|
|
908
|
+
FactId,
|
|
909
|
+
property(Subject, Property, Operator, ValueType, Value, Unit, Scope, Polarity)
|
|
910
|
+
) :-
|
|
911
|
+
kb:fact_property_tuple(
|
|
912
|
+
FactId,
|
|
913
|
+
Subject,
|
|
914
|
+
Property,
|
|
915
|
+
Operator,
|
|
916
|
+
ValueType,
|
|
917
|
+
Value,
|
|
918
|
+
Unit,
|
|
919
|
+
Scope,
|
|
920
|
+
Polarity
|
|
921
|
+
).
|
|
922
|
+
|
|
923
|
+
ground_fact_claim_key(FactId, ExpectedKind, ClaimKey) :-
|
|
924
|
+
kb_entity(FactId, fact, Props),
|
|
925
|
+
memberchk(fact_kind=RawKind, Props),
|
|
926
|
+
normalize_term_atom(RawKind, ExpectedKind),
|
|
927
|
+
memberchk(claim_key=RawClaimKey, Props),
|
|
928
|
+
normalize_term_atom(RawClaimKey, ClaimKey),
|
|
929
|
+
memberchk(claim_text=RawClaimText, Props),
|
|
930
|
+
normalize_term_atom(RawClaimText, ClaimText),
|
|
931
|
+
ClaimText \= ''.
|
|
932
|
+
|
|
755
933
|
strict_readiness_has_fact_link(ReqId) :-
|
|
756
934
|
kb_relationship(constrains, ReqId, FactId),
|
|
757
935
|
kb_entity(FactId, fact, _),
|
|
@@ -824,6 +1002,8 @@ check_all_with_options(ViolationsDict, RequireAdr) :-
|
|
|
824
1002
|
check_strict_fact_shape(StrictFactShape),
|
|
825
1003
|
check_strict_req_fact_pairing(StrictReqFactPairing),
|
|
826
1004
|
check_strict_readiness(StrictReadiness),
|
|
1005
|
+
check_predicate_verifiability(PredicateVerifiability),
|
|
1006
|
+
check_logic_coverage(LogicCoverage),
|
|
827
1007
|
ViolationsDict = _{
|
|
828
1008
|
must_priority_coverage: MustPriority,
|
|
829
1009
|
symbol_coverage: SymbolCoverage,
|
|
@@ -835,7 +1015,9 @@ check_all_with_options(ViolationsDict, RequireAdr) :-
|
|
|
835
1015
|
domain_contradictions: Contradictions,
|
|
836
1016
|
strict_fact_shape: StrictFactShape,
|
|
837
1017
|
strict_req_fact_pairing: StrictReqFactPairing,
|
|
838
|
-
strict_readiness: StrictReadiness
|
|
1018
|
+
strict_readiness: StrictReadiness,
|
|
1019
|
+
predicate_verifiability: PredicateVerifiability,
|
|
1020
|
+
logic_coverage: LogicCoverage
|
|
839
1021
|
}.
|
|
840
1022
|
|
|
841
1023
|
%% violations_dict_to_json(+ViolationsDict, -JsonDict)
|
package/src/discovery.pl
CHANGED
|
@@ -131,6 +131,7 @@ requirement_coverage_row(Tags, IncludeTransitive, Row) :-
|
|
|
131
131
|
count_direct_symbols(Id, DirectSymbolCount),
|
|
132
132
|
count_transitive_symbols(Id, IncludeTransitive, TransitiveSymbolCount),
|
|
133
133
|
requirement_coverage_state(Id, Props, Gaps, Evaluated, CoverageStatus),
|
|
134
|
+
requirement_coverage_depth(Id, ScenarioCount, CoverageDepth, CoverageEvidence),
|
|
134
135
|
Row = _{
|
|
135
136
|
id: Id,
|
|
136
137
|
type: req,
|
|
@@ -143,7 +144,13 @@ requirement_coverage_row(Tags, IncludeTransitive, Row) :-
|
|
|
143
144
|
transitiveSymbolCount: TransitiveSymbolCount,
|
|
144
145
|
gaps: Gaps,
|
|
145
146
|
evaluated: Evaluated,
|
|
146
|
-
coverageStatus: CoverageStatus
|
|
147
|
+
coverageStatus: CoverageStatus,
|
|
148
|
+
coverageDepth: CoverageDepth,
|
|
149
|
+
coverage_depth: CoverageDepth,
|
|
150
|
+
directTests: CoverageEvidence.directTests,
|
|
151
|
+
scenarioTests: CoverageEvidence.scenarioTests,
|
|
152
|
+
testStatuses: CoverageEvidence.testStatuses,
|
|
153
|
+
verificationScopes: CoverageEvidence.verificationScopes
|
|
147
154
|
}.
|
|
148
155
|
|
|
149
156
|
symbol_coverage_row(Tags, Row) :-
|
|
@@ -232,6 +239,125 @@ requirement_test_count(Id, Count) :-
|
|
|
232
239
|
sort(Combined0, Combined),
|
|
233
240
|
length(Combined, Count).
|
|
234
241
|
|
|
242
|
+
requirement_coverage_depth(Id, ScenarioCount, CoverageDepth, Evidence) :-
|
|
243
|
+
requirement_direct_tests(Id, DirectTests),
|
|
244
|
+
requirement_scenario_tests(Id, ScenarioTests),
|
|
245
|
+
append(DirectTests, ScenarioTests, AllTests0),
|
|
246
|
+
sort(AllTests0, AllTests),
|
|
247
|
+
passing_tests(DirectTests, PassingDirectTests),
|
|
248
|
+
passing_tests(ScenarioTests, PassingScenarioTests),
|
|
249
|
+
passing_tests(AllTests, PassingTests),
|
|
250
|
+
test_statuses(AllTests, TestStatuses),
|
|
251
|
+
test_scopes(AllTests, VerificationScopes),
|
|
252
|
+
Evidence = _{
|
|
253
|
+
directTests: DirectTests,
|
|
254
|
+
scenarioTests: ScenarioTests,
|
|
255
|
+
testStatuses: TestStatuses,
|
|
256
|
+
verificationScopes: VerificationScopes
|
|
257
|
+
},
|
|
258
|
+
classify_coverage_depth(
|
|
259
|
+
ScenarioCount,
|
|
260
|
+
AllTests,
|
|
261
|
+
PassingTests,
|
|
262
|
+
PassingDirectTests,
|
|
263
|
+
PassingScenarioTests,
|
|
264
|
+
CoverageDepth
|
|
265
|
+
).
|
|
266
|
+
|
|
267
|
+
requirement_direct_tests(Id, Tests) :-
|
|
268
|
+
findall(TestId, direct_requirement_test(Id, TestId), Tests0),
|
|
269
|
+
sort(Tests0, Tests).
|
|
270
|
+
|
|
271
|
+
direct_requirement_test(Id, TestId) :-
|
|
272
|
+
kb_relationship(verified_by, Id, TestId),
|
|
273
|
+
kb_entity(TestId, test, _).
|
|
274
|
+
direct_requirement_test(Id, TestId) :-
|
|
275
|
+
kb_relationship(validates, TestId, Id),
|
|
276
|
+
kb_entity(TestId, test, _).
|
|
277
|
+
direct_requirement_test(Id, TestId) :-
|
|
278
|
+
kb_relationship(covered_by, Id, TestId),
|
|
279
|
+
kb_entity(TestId, test, _).
|
|
280
|
+
|
|
281
|
+
requirement_scenario_tests(Id, Tests) :-
|
|
282
|
+
findall(TestId, scenario_requirement_test(Id, TestId), Tests0),
|
|
283
|
+
sort(Tests0, Tests).
|
|
284
|
+
|
|
285
|
+
scenario_requirement_test(Id, TestId) :-
|
|
286
|
+
kb_relationship(specified_by, Id, ScenarioId),
|
|
287
|
+
scenario_test(ScenarioId, TestId).
|
|
288
|
+
|
|
289
|
+
scenario_test(ScenarioId, TestId) :-
|
|
290
|
+
kb_relationship(validates, TestId, ScenarioId),
|
|
291
|
+
kb_entity(TestId, test, _).
|
|
292
|
+
scenario_test(ScenarioId, TestId) :-
|
|
293
|
+
kb_relationship(verified_by, ScenarioId, TestId),
|
|
294
|
+
kb_entity(TestId, test, _).
|
|
295
|
+
|
|
296
|
+
passing_tests(Tests, PassingTests) :-
|
|
297
|
+
include(passing_test, Tests, PassingTests).
|
|
298
|
+
|
|
299
|
+
passing_test(TestId) :-
|
|
300
|
+
kb_entity(TestId, test, Props),
|
|
301
|
+
memberchk(status=StatusRaw, Props),
|
|
302
|
+
source_value_atom(StatusRaw, passing).
|
|
303
|
+
|
|
304
|
+
test_statuses(Tests, Statuses) :-
|
|
305
|
+
findall(Status, (member(TestId, Tests), test_status(TestId, Status)), Statuses0),
|
|
306
|
+
sort(Statuses0, Statuses).
|
|
307
|
+
|
|
308
|
+
test_status(TestId, Status) :-
|
|
309
|
+
kb_entity(TestId, test, Props),
|
|
310
|
+
memberchk(status=StatusRaw, Props),
|
|
311
|
+
source_value_atom(StatusRaw, Status).
|
|
312
|
+
|
|
313
|
+
test_scopes(Tests, Scopes) :-
|
|
314
|
+
findall(Scope, (member(TestId, Tests), test_scope(TestId, Scope)), Scopes0),
|
|
315
|
+
sort(Scopes0, Scopes).
|
|
316
|
+
|
|
317
|
+
test_scope(TestId, Scope) :-
|
|
318
|
+
kb_entity(TestId, test, Props),
|
|
319
|
+
( memberchk(verification_scope=ScopeRaw, Props)
|
|
320
|
+
-> source_value_atom(ScopeRaw, Scope)
|
|
321
|
+
; legacy_e2e_test(Props)
|
|
322
|
+
-> Scope = end_to_end
|
|
323
|
+
; Scope = unknown
|
|
324
|
+
).
|
|
325
|
+
|
|
326
|
+
legacy_e2e_test(Props) :-
|
|
327
|
+
legacy_e2e_tag(Props),
|
|
328
|
+
!.
|
|
329
|
+
legacy_e2e_test(Props) :-
|
|
330
|
+
memberchk(source=SourceRaw, Props),
|
|
331
|
+
source_value_atom(SourceRaw, Source),
|
|
332
|
+
downcase_atom(Source, LowercaseSource),
|
|
333
|
+
sub_atom(LowercaseSource, _, _, _, 'e2e').
|
|
334
|
+
|
|
335
|
+
legacy_e2e_tag(Props) :-
|
|
336
|
+
memberchk(tags=Tags, Props),
|
|
337
|
+
member(TagRaw, Tags),
|
|
338
|
+
source_value_atom(TagRaw, Tag),
|
|
339
|
+
downcase_atom(Tag, e2e).
|
|
340
|
+
|
|
341
|
+
classify_coverage_depth(_ScenarioCount, _AllTests, _PassingTests, PassingDirectTests, _PassingScenarioTests, direct_passing_e2e) :-
|
|
342
|
+
member(TestId, PassingDirectTests),
|
|
343
|
+
test_scope(TestId, end_to_end),
|
|
344
|
+
!.
|
|
345
|
+
classify_coverage_depth(_ScenarioCount, _AllTests, _PassingTests, _PassingDirectTests, PassingScenarioTests, scenario_passing_e2e) :-
|
|
346
|
+
member(TestId, PassingScenarioTests),
|
|
347
|
+
test_scope(TestId, end_to_end),
|
|
348
|
+
!.
|
|
349
|
+
classify_coverage_depth(_ScenarioCount, _AllTests, PassingTests, _PassingDirectTests, _PassingScenarioTests, unit_only) :-
|
|
350
|
+
PassingTests \= [],
|
|
351
|
+
forall(member(TestId, PassingTests), test_scope(TestId, unit)),
|
|
352
|
+
!.
|
|
353
|
+
classify_coverage_depth(_ScenarioCount, AllTests, _PassingTests, _PassingDirectTests, _PassingScenarioTests, open_or_nonpassing_tests_only) :-
|
|
354
|
+
AllTests \= [],
|
|
355
|
+
!.
|
|
356
|
+
classify_coverage_depth(ScenarioCount, _AllTests, _PassingTests, _PassingDirectTests, _PassingScenarioTests, scenario_only_no_test) :-
|
|
357
|
+
ScenarioCount > 0,
|
|
358
|
+
!.
|
|
359
|
+
classify_coverage_depth(_ScenarioCount, _AllTests, _PassingTests, _PassingDirectTests, _PassingScenarioTests, no_test_evidence).
|
|
360
|
+
|
|
235
361
|
count_direct_symbols(Id, Count) :-
|
|
236
362
|
findall(SymbolId, kb_relationship(implements, SymbolId, Id), Symbols0),
|
|
237
363
|
sort(Symbols0, Symbols),
|
package/src/kb.pl
CHANGED
|
@@ -45,6 +45,7 @@
|
|
|
45
45
|
contradicting_reqs/3,
|
|
46
46
|
check_req_contradiction/1,
|
|
47
47
|
normalize_term_atom/2,
|
|
48
|
+
normalize_term_atom_list/2,
|
|
48
49
|
changeset/4, % Export for testing
|
|
49
50
|
kb_uri/1
|
|
50
51
|
]).
|
|
@@ -636,7 +637,7 @@ value_to_literal(Key, Value, Literal) :-
|
|
|
636
637
|
; string(Value)
|
|
637
638
|
-> Literal = Value^^'http://www.w3.org/2001/XMLSchema#string'
|
|
638
639
|
; is_list(Value)
|
|
639
|
-
->
|
|
640
|
+
-> with_output_to(atom(ListStr), write_term(Value, [quoted(true)])),
|
|
640
641
|
Literal = ListStr^^'http://www.w3.org/2001/XMLSchema#string'
|
|
641
642
|
; format(atom(Str), '~w', [Value]),
|
|
642
643
|
Literal = Str^^'http://www.w3.org/2001/XMLSchema#string'
|
|
@@ -1005,6 +1006,26 @@ req_conflict(ReqA, ReqB, Reason) :-
|
|
|
1005
1006
|
; property_conflict(SubjectKey, PropertyKey, OpA, ValTypeA, ValA, UnitA, PolarityA,
|
|
1006
1007
|
OpB, ValTypeB, ValB, UnitB, PolarityB, Reason)
|
|
1007
1008
|
).
|
|
1009
|
+
req_conflict(ReqA, ReqB, Reason) :-
|
|
1010
|
+
effective_req_predicate(ReqA, FactA, Namespace, Name, Args, PolarityA),
|
|
1011
|
+
effective_req_predicate(ReqB, FactB, Namespace, Name, Args, PolarityB),
|
|
1012
|
+
FactA \= FactB,
|
|
1013
|
+
opposite_predicate_polarity(PolarityA, PolarityB),
|
|
1014
|
+
atomic_list_concat(Args, ',', ArgsText),
|
|
1015
|
+
format(
|
|
1016
|
+
string(Reason),
|
|
1017
|
+
"Predicate conflict on ~w:~w(~w): ~w asserts ~w while ~w asserts ~w",
|
|
1018
|
+
[Namespace, Name, ArgsText, ReqA, PolarityA, ReqB, PolarityB]
|
|
1019
|
+
).
|
|
1020
|
+
|
|
1021
|
+
%% effective_req_predicate(+ReqId, -FactId, -Namespace, -Name, -Args, -Polarity)
|
|
1022
|
+
% Ground predicate claim required by a requirement.
|
|
1023
|
+
effective_req_predicate(ReqId, FactId, Namespace, Name, Args, Polarity) :-
|
|
1024
|
+
kb_relationship(requires_predicate, ReqId, FactId),
|
|
1025
|
+
predicate_fact(FactId, Namespace, Name, Args, Polarity).
|
|
1026
|
+
|
|
1027
|
+
opposite_predicate_polarity(assert, deny).
|
|
1028
|
+
opposite_predicate_polarity(deny, assert).
|
|
1008
1029
|
|
|
1009
1030
|
%% fact_subject_key(+FactId, -SubjectKey)
|
|
1010
1031
|
% Extract the normalized subject key for strict subject facts.
|
package/src/sparql_client.pl
CHANGED
|
@@ -44,7 +44,10 @@ validate_remote_endpoint(Endpoint) :-
|
|
|
44
44
|
|
|
45
45
|
validate_select_query(Query) :-
|
|
46
46
|
normalize_space(atom(Normalized), Query),
|
|
47
|
-
|
|
47
|
+
split_string(Normalized, "\n", " \t\r", RawLines),
|
|
48
|
+
strip_sparql_prelude(RawLines, RemainingLines),
|
|
49
|
+
atomic_list_concat(RemainingLines, ' ', Remainder),
|
|
50
|
+
downcase_atom(Remainder, Lower),
|
|
48
51
|
sub_atom(Lower, 0, 6, After, 'select'),
|
|
49
52
|
( After =:= 0
|
|
50
53
|
; sub_atom(Lower, 6, 1, _, ' ')
|
|
@@ -54,6 +57,25 @@ validate_select_query(Query) :-
|
|
|
54
57
|
validate_select_query(Query) :-
|
|
55
58
|
throw(error(domain_error(sparql_select_query, Query), _)).
|
|
56
59
|
|
|
60
|
+
strip_sparql_prelude([], []).
|
|
61
|
+
strip_sparql_prelude([Line | Rest], Result) :-
|
|
62
|
+
normalize_space(atom(Trimmed), Line),
|
|
63
|
+
( Trimmed == ''
|
|
64
|
+
-> strip_sparql_prelude(Rest, Result)
|
|
65
|
+
; is_prelude_line(Trimmed)
|
|
66
|
+
-> strip_sparql_prelude(Rest, Result)
|
|
67
|
+
; Result = [Trimmed | Rest]
|
|
68
|
+
).
|
|
69
|
+
|
|
70
|
+
is_prelude_line(Line) :-
|
|
71
|
+
downcase_atom(Line, Lower),
|
|
72
|
+
split_string(Lower, " ", " \t", Tokens0),
|
|
73
|
+
exclude(=("") , Tokens0, Tokens),
|
|
74
|
+
Tokens = [FirstToken | _],
|
|
75
|
+
( FirstToken == "prefix"
|
|
76
|
+
; FirstToken == "base"
|
|
77
|
+
).
|
|
78
|
+
|
|
57
79
|
endpoint_host(Endpoint, Host) :-
|
|
58
80
|
strip_endpoint_scheme(Endpoint, WithoutScheme),
|
|
59
81
|
segment_before(WithoutScheme, '/', WithoutPath),
|
package/src/status.pl
CHANGED
|
@@ -117,6 +117,8 @@ documentation_markdown_untracked(WorkspaceRoot) :-
|
|
|
117
117
|
documentation_markdown_file(Path, Path) :-
|
|
118
118
|
exists_file(Path),
|
|
119
119
|
file_name_extension(_, md, Path),
|
|
120
|
+
entity_documentation_file(Path),
|
|
121
|
+
\+ ignored_documentation_file(Path),
|
|
120
122
|
!.
|
|
121
123
|
documentation_markdown_file(Path, FilePath) :-
|
|
122
124
|
exists_directory(Path),
|
|
@@ -138,6 +140,9 @@ known_source_path(RelativePath) :-
|
|
|
138
140
|
repo_relative_source(SourceAtom, RelativePath).
|
|
139
141
|
|
|
140
142
|
directory_tree_newer(Path, SnapshotTime) :-
|
|
143
|
+
exists_file(Path),
|
|
144
|
+
entity_documentation_file(Path),
|
|
145
|
+
\+ ignored_documentation_file(Path),
|
|
141
146
|
time_file(Path, EntryTime),
|
|
142
147
|
EntryTime > SnapshotTime,
|
|
143
148
|
!.
|
|
@@ -212,5 +217,15 @@ source_value_atom(Val, Atom) :-
|
|
|
212
217
|
term_string(Val, Str),
|
|
213
218
|
atom_string(Atom, Str).
|
|
214
219
|
|
|
220
|
+
ignored_documentation_file(Path) :-
|
|
221
|
+
file_base_name(Path, 'README.md').
|
|
222
|
+
|
|
223
|
+
entity_documentation_file(Path) :-
|
|
224
|
+
read_file_to_string(Path, Content, []),
|
|
225
|
+
sub_string(Content, 0, 3, _, "---"),
|
|
226
|
+
sub_string(Content, _, _, _, "id:"),
|
|
227
|
+
sub_string(Content, _, _, _, "title:"),
|
|
228
|
+
sub_string(Content, _, _, _, "status:").
|
|
229
|
+
|
|
215
230
|
dict_json_string(Dict, JsonString) :-
|
|
216
231
|
with_output_to(string(JsonString), json_write_dict(current_output, Dict, [])).
|