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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kibi-core",
3
- "version": "0.6.4",
3
+ "version": "0.7.1",
4
4
  "private": false,
5
5
  "description": "Core Prolog modules and RDF graph logic for Kibi",
6
6
  "type": "module",
@@ -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
 
@@ -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
- \+ (memberchk(value_string=_, Props); memberchk(value_int=_, Props);
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
- -> format(atom(ListStr), '~w', [Value]),
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.
@@ -44,7 +44,10 @@ validate_remote_endpoint(Endpoint) :-
44
44
 
45
45
  validate_select_query(Query) :-
46
46
  normalize_space(atom(Normalized), Query),
47
- downcase_atom(Normalized, Lower),
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, [])).